├── .gitignore ├── index.js ├── lib ├── node-internals.js ├── tls-socket.js ├── interceptor.js └── stream-handles.js ├── .github └── workflows │ ├── release.yml │ └── test.yml ├── index.test-d.ts ├── test ├── api-test.js └── net-test.js ├── LICENSE.md ├── index.d.ts ├── package.json ├── CONTRIBUTING.md ├── README.md └── CODE_OF_CONDUCT.md /.gitignore: -------------------------------------------------------------------------------- 1 | .nyc_output/ 2 | coverage/ 3 | node_modules/ -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import NetInterceptor from "./lib/interceptor.js"; 4 | export default new NetInterceptor(); 5 | import { kRemote } from "./lib/stream-handles.js"; 6 | export { kRemote }; 7 | -------------------------------------------------------------------------------- /lib/node-internals.js: -------------------------------------------------------------------------------- 1 | import { _normalizeArgs as normalizeConnectArgs } from "node:net"; 2 | 3 | const UV_EOF = process.binding("uv").UV_EOF; 4 | const STREAM_STATE = process.binding("stream_wrap").streamBaseState; 5 | const STREAM_BYTES_READ = process.binding("stream_wrap").kReadBytesOrError; 6 | 7 | export default { 8 | normalizeConnectArgs, 9 | UV_EOF, 10 | STREAM_STATE, 11 | STREAM_BYTES_READ, 12 | }; 13 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | "on": 3 | push: 4 | branches: 5 | - "*.x" 6 | - main 7 | - next 8 | - beta 9 | jobs: 10 | release: 11 | name: release 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: lts/* 18 | cache: npm 19 | - run: npm ci 20 | - run: npx semantic-release 21 | env: 22 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 23 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 24 | -------------------------------------------------------------------------------- /lib/tls-socket.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import net from "node:net"; 4 | import tls from "node:tls"; 5 | 6 | /** 7 | * TlsSocket is not extending `tls.TLSSocket` because the 8 | * logic is a lot different to `net.Socket`. So we inherit 9 | * from `net.Socket` instead and then only apply the instances 10 | * methods from `tls.TLSSocket`. 11 | */ 12 | export default class NetInterceptorTlsSocket extends net.Socket { 13 | encrypted = true; 14 | authorized = true; 15 | 16 | constructor(...args) { 17 | super(...args); 18 | Object.assign(this, tls.TLSSocket.prototype); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /index.test-d.ts: -------------------------------------------------------------------------------- 1 | import * as net from "net"; 2 | 3 | import { expectType } from "tsd"; 4 | 5 | import netInterceptor, { kRemote } from "./index.js"; 6 | 7 | netInterceptor.stop(); 8 | 9 | netInterceptor.addListener("connect", (socket, options, bypass): void => { 10 | expectType<() => void>(bypass); 11 | 12 | expectType(options.port); 13 | expectType(options.host); 14 | }); 15 | 16 | netInterceptor.addListener("connection", (socket, options): void => { 17 | expectType(socket); 18 | 19 | expectType(options.port); 20 | expectType(options.host); 21 | }); 22 | 23 | expectType(kRemote); 24 | -------------------------------------------------------------------------------- /test/api-test.js: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from "node:events"; 2 | 3 | import { test } from "uvu"; 4 | import * as assert from "uvu/assert"; 5 | 6 | import netInterceptor from "../index.js"; 7 | 8 | test("netInterceptor.start is a function", () => { 9 | assert.instance(netInterceptor.start, Function); 10 | }); 11 | test("netInterceptor.stop is a function", () => { 12 | assert.instance(netInterceptor.stop, Function); 13 | }); 14 | 15 | test("multiple calls of start and stop are no-ops", () => { 16 | netInterceptor.start(); 17 | netInterceptor.start(); 18 | netInterceptor.stop(); 19 | netInterceptor.stop(); 20 | }); 21 | 22 | test("netInterceptor.addListener must be an alias to EventEmitter.prototype.addListener", () => { 23 | assert.equal(netInterceptor.addListener, EventEmitter.prototype.addListener); 24 | }); 25 | 26 | test("netInterceptor.off must be an alias to EventEmitter.prototype.removeListener", () => { 27 | assert.equal(netInterceptor.off, EventEmitter.prototype.removeListener); 28 | }); 29 | 30 | test.run(); 31 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | "on": 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | types: 8 | - opened 9 | - synchronize 10 | jobs: 11 | test_matrix: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | node_version: 16 | - "14" 17 | - "16" 18 | - "17" 19 | steps: 20 | - uses: actions/checkout@v3 21 | - name: Use Node.js ${{ matrix.node_version }} 22 | uses: actions/setup-node@v3 23 | with: 24 | node-version: ${{ matrix.node_version }} 25 | cache: npm 26 | - run: npm ci 27 | - run: npm run test:code 28 | test: 29 | runs-on: ubuntu-latest 30 | needs: test_matrix 31 | steps: 32 | - uses: actions/checkout@v3 33 | - uses: actions/setup-node@v3 34 | with: 35 | node-version: lts/* 36 | cache: npm 37 | - run: npm ci 38 | - run: npm run test:tsc 39 | - run: npm run test:tsd 40 | - run: npm run lint 41 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Andri Möll 2 | Copyright (C) 2021 Gregor Marytnus 3 | 4 | This program is free software: you can redistribute it and/or modify it under 5 | the terms of the GNU Affero General Public License as published by the Free 6 | Software Foundation, either version 3 of the License, or any later version. 7 | 8 | Additional permission under the GNU Affero GPL version 3 section 7: 9 | If you modify this Program, or any covered work, by linking or 10 | combining it with other code, such other code is not for that reason 11 | alone subject to any of the requirements of the GNU Affero GPL version 3. 12 | 13 | In summary: 14 | 15 | - You can use this program for no cost. 16 | - You can use this program for both personal and commercial reasons. 17 | - You do not have to share your own program's code which uses this program. 18 | - You have to share modifications (e.g bug-fixes) you've made to this program. 19 | 20 | For the full copy of the GNU Affero General Public License see: 21 | http://www.gnu.org/licenses. 22 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import net from "node:net"; 2 | import tls from "node:net"; 3 | import { EventEmitter } from "node:events"; 4 | 5 | declare const interceptor: NetInterceptor; 6 | export default interceptor; 7 | 8 | export declare const kRemote: Symbol; 9 | 10 | declare class NetInterceptor extends EventEmitter { 11 | /** 12 | * Start intercepting net/tls socket connections. 13 | * No-op if already recording. 14 | */ 15 | start: () => this; 16 | 17 | /** 18 | * Stop intercepting net/tls socket connections. 19 | * No-op if not recording. 20 | */ 21 | stop: () => this; 22 | 23 | /** 24 | * Subscribe to the "connect" event, emitted each time a net/tls socket connets 25 | */ 26 | addListener(event: "connect", listener: ConnectEventListener): this; 27 | addListener(event: "connection", listener: ConnectionEventListener): this; 28 | 29 | /** 30 | * Unsubscribe from an event using the same listener function 31 | * that was used when subscribing. 32 | */ 33 | removeListener(event: NetInterceptorEvents, listener: () => void): this; 34 | 35 | /** 36 | * Remove all listeners from all events 37 | */ 38 | removeAllListeners(): this; 39 | 40 | /** 41 | * Remove all listeners from an events 42 | */ 43 | removeAllListeners(event: NetInterceptorEvents): this; 44 | } 45 | 46 | type NetInterceptorEvents = "connect" | "connection"; 47 | 48 | type NetConnectOptions = net.TcpNetConnectOpts; 49 | 50 | interface ConnectEventListener { 51 | ( 52 | socket: net.Socket | tls.Socket, 53 | options: NetConnectOptions, 54 | bypass: () => void 55 | ): void; 56 | } 57 | 58 | interface ConnectionEventListener { 59 | (socket: net.Socket | tls.Socket, options: NetConnectOptions): void; 60 | } 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@gr2m/net-interceptor", 3 | "publishConfig": { 4 | "access": "public" 5 | }, 6 | "version": "0.0.0-development", 7 | "type": "module", 8 | "licenses": [ 9 | { 10 | "type": "LAGPL", 11 | "url": "https://github.com/gr2m/node-net-interceptor/blob/master/LICENSE.md" 12 | } 13 | ], 14 | "description": "Intercept and mock outgoing network TCP/TLS connections", 15 | "author": "Gregor Martynus (https://github.com/gr2m)", 16 | "contributors": [ 17 | { 18 | "name": "Andri Möll", 19 | "email": "andri@dot.ee", 20 | "url": "http://themoll.com" 21 | } 22 | ], 23 | "keywords": [ 24 | "net", 25 | "intercept", 26 | "interception", 27 | "network", 28 | "socket", 29 | "tcp", 30 | "tls", 31 | "test" 32 | ], 33 | "repository": "github:gr2m/node-net-interceptor", 34 | "exports": "./index.js", 35 | "scripts": { 36 | "test": "npm run test:code && npm run test:tsc && npm run test:tsd && npm run lint", 37 | "test:code": "c8 --100 uvu", 38 | "test:tsc": "tsc --allowJs --noEmit --esModuleInterop --skipLibCheck --lib es2020 index.js", 39 | "test:tsd": "tsd", 40 | "test:workspaces": "npm test --workspaces --if-present", 41 | "lint": "prettier --check \"*.{js,json,ts,md}\" \".github/**/*.yml\"", 42 | "lint:fix": "prettier --write \"*.{js,json,ts,md}\" \".github/**/*.yml\"", 43 | "coverage": "c8 report --reporter html", 44 | "postcoverage": "open-cli coverage/index.html" 45 | }, 46 | "devDependencies": { 47 | "@types/node": "^16.11.11", 48 | "c8": "^7.10.0", 49 | "open-cli": "^7.0.1", 50 | "prettier": "^2.5.0", 51 | "sinon": "^12.0.1", 52 | "tsd": "^0.19.0", 53 | "typescript": "^4.5.2", 54 | "uvu": "^0.5.2" 55 | }, 56 | "engines": { 57 | "node": ">= 14" 58 | }, 59 | "release": { 60 | "branches": [ 61 | "+([0-9]).x", 62 | "main", 63 | "next", 64 | { 65 | "name": "beta", 66 | "prerelease": true 67 | } 68 | ] 69 | }, 70 | "renovate": { 71 | "extends": [ 72 | "github>gr2m/.github" 73 | ] 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you for considering to contribute to `@gr2m/net-interceptor` 💖 4 | 5 | Please note that this project is released with a [Contributor Code of Conduct][./code_of_conduct.md]. 6 | By participating you agree to abide by its terms. 7 | 8 | ## Setup 9 | 10 | Node.js 14 or higher is required. Install it from https://nodejs.org/en/. [GitHub's `gh` CLI](https://cli.github.com/) is recommended for the initial setup 11 | 12 | 1. Fork this repository and clone it to your local machine. Using `gh` you can do this 13 | 14 | ``` 15 | gh repo fork gr2m/net-interceptor 16 | ``` 17 | 18 | 2. After cloning and changing into the `@gr2m/net-interceptor` directory, install dependencies and run the tests 19 | 20 | ``` 21 | npm install 22 | npm test 23 | ``` 24 | 25 | Few notes 26 | 27 | - `npm test` runs all kind of tests. You can run the code tests in isolation with `npm run test:code`. Use `npm run` to see all available scripts. 28 | - If coverage drops, run `npm run coverage` to open a coverage report in your browser. 29 | - Make sure that update types in `index.d.ts` that reflect any features / fixes you might have implemented. 30 | 31 | ## Issues before pull requests 32 | 33 | Unless the change is trivial such as a type, please [open an issue first](https://github.com/gr2m/node-net-interceptor/issues/new) before starting a pull request for a bug fix or a new feature. 34 | 35 | After you cloned your fork, create a new branch and implement the changes in them. To start a pull request, you can use the `gh` CLI 36 | 37 | ``` 38 | gh pr create 39 | ``` 40 | 41 | ## Maintainers only 42 | 43 | ### Merging the Pull Request & releasing a new version 44 | 45 | Releases are automated using [semantic-release](https://github.com/semantic-release/semantic-release). 46 | The following commit message conventions determine which version is released: 47 | 48 | 1. `fix: ...` or `fix(scope name): ...` prefix in subject: bumps fix version, e.g. `1.2.3` → `1.2.4` 49 | 2. `feat: ...` or `feat(scope name): ...` prefix in subject: bumps feature version, e.g. `1.2.3` → `1.3.0` 50 | 3. `BREAKING CHANGE:` in body: bumps breaking version, e.g. `1.2.3` → `2.0.0` 51 | 52 | Only one version number is bumped at a time, the highest version change trumps the others. 53 | Besides, publishing a new version to npm, semantic-release also creates a git tag and release 54 | on GitHub, generates changelogs from the commit messages and puts them into the release notes. 55 | 56 | If the pull request looks good but does not follow the commit conventions, update the pull request title and use the Squash & merge button, at which point you can set a custom commit message. 57 | -------------------------------------------------------------------------------- /lib/interceptor.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import net from "node:net"; 4 | import tls from "node:tls"; 5 | import { Agent } from "node:http"; 6 | import { EventEmitter } from "node:events"; 7 | 8 | import NODE_INTERNALS from "./node-internals.js"; 9 | 10 | import MitmTlsSocket from "./tls-socket.js"; 11 | import createRequestResponseHandlePair, { kRemote } from "./stream-handles.js"; 12 | 13 | let isIntercepting = false; 14 | let didPatchConnect = false; 15 | 16 | export default class NetInterceptor extends EventEmitter { 17 | /**y 18 | * @returns {NetInterceptor} 19 | */ 20 | start() { 21 | if (isIntercepting) return; 22 | isIntercepting = true; 23 | 24 | if (didPatchConnect) return; 25 | didPatchConnect = true; 26 | 27 | const netConnect = this.connect.bind(this, net.connect, net.Socket); 28 | const tlsConnect = this.connect.bind(this, tls.connect, MitmTlsSocket); 29 | 30 | net.connect = netConnect; 31 | net.createConnection = netConnect; 32 | // @ts-expect-error - createConnection is not typed 33 | Agent.prototype.createConnection = netConnect; 34 | tls.connect = tlsConnect; 35 | 36 | return this; 37 | } 38 | 39 | /** 40 | * @returns {NetInterceptor} 41 | */ 42 | stop() { 43 | isIntercepting = false; 44 | return this; 45 | } 46 | 47 | /** 48 | * This method is called when a socket is established, either through `net`, 49 | * `tls`, or an `http.Agent` prototype. We create a new net/tls socket 50 | * and give the opportunity to bypass the interception in a `connect` listener. 51 | * 52 | * If the request is intercepted, we call the original connect method, otherwise 53 | * we create a mock `response` socket and emit the `connection` event. 54 | */ 55 | connect(originalConnect, Socket, ...args) { 56 | if (!isIntercepting) { 57 | return originalConnect.call(this, ...args); 58 | } 59 | const [options, callback] = NODE_INTERNALS.normalizeConnectArgs(args); 60 | const { requestHandle, responseHandle } = createRequestResponseHandlePair(); 61 | 62 | // request 63 | const requestSocket = new Socket({ 64 | handle: requestHandle, 65 | ...options, 66 | }); 67 | 68 | // give opportunity to bypass the intercept 69 | let bypassed = false; 70 | 71 | this.emit("connect", requestSocket, options, () => (bypassed = true)); 72 | if (bypassed) { 73 | return originalConnect.call(this, ...args); 74 | } 75 | 76 | // response 77 | const responseSocket = new Socket({ handle: responseHandle }); 78 | 79 | // Add a cross reference from the request socket to the response socket 80 | requestSocket[kRemote] = responseSocket; 81 | 82 | this.emit("connection", responseSocket, options); 83 | 84 | // Ensure connect is emitted asynchronously, otherwise it would be impossible 85 | // to listen to it after calling net.connect or listening to it after the 86 | // ClientRequest emits "socket". 87 | setTimeout(() => { 88 | requestSocket.emit("connect"); 89 | responseSocket.emit("connect"); 90 | }); 91 | 92 | const callbackMethod = Socket === net.Socket ? "connect" : "secureConnect"; 93 | if (callback) requestSocket.once(callbackMethod, callback); 94 | 95 | // If a TLS request is intercepted, we simulate a successful 96 | // handshake by emiting a "secureConnect" event asyncronously. 97 | if (callbackMethod === "secureConnect") { 98 | setTimeout(requestSocket.emit.bind(requestSocket, "secureConnect")); 99 | } 100 | 101 | return requestSocket; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `@gr2m/net-interceptor` 2 | 3 | [![Test](https://github.com/gr2m/node-net-interceptor/actions/workflows/test.yml/badge.svg)](https://github.com/gr2m/node-net-interceptor/actions/workflows/test.yml) 4 | 5 | > Intercept outgoing network TCP/TLS connections 6 | 7 | ## Install 8 | 9 | ``` 10 | npm install @gr2m/net-interceptor 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```js 16 | import netInterceptor from "@gr2m/net-interceptor"; 17 | 18 | netInterceptor.start(); 19 | netInterceptor.on("connect", (socket, options, bypass) => { 20 | // call bypass() to continue the unintercepted connection 21 | if (options.host === "db.example.com") return bypass(); 22 | }); 23 | 24 | netInterceptor.on("connection", (socket) => { 25 | // do something with the socket 26 | socket.write("Hello from @gr2m/net-interceptor!"); 27 | }); 28 | ``` 29 | 30 | ## API 31 | 32 | `netInterceptor` is a singleton API. 33 | 34 | ### `netInterceptor.start()` 35 | 36 | Hooks into the request life cycle and emits `connect` events for each socket that connects to a server as well as `connection` events for all intercepted sockets. 37 | 38 | ### `netInterceptor.stop()` 39 | 40 | Stops interceptiong. No `connect` or `connection` events will be emitted. 41 | 42 | ### `netInterceptor.addListener(event, listener)` 43 | 44 | #### `connect` event 45 | 46 | The `listener` callback is called with 3 arguments 47 | 48 | - `socket`: the intercepted net or TLS socket 49 | - `options`: socket options: `{port, /* host, localAddress, localPort, family, allowHalfOpen */}` 50 | - `bypass`: a function to call to continue the unintercepted connection 51 | 52 | #### `connection` event 53 | 54 | The `listener` callback is called with 2 arguments 55 | 56 | - `socket`: the response net or TLS socket 57 | - `options`: socket options: `{port, /* host, localAddress, localPort, family, allowHalfOpen */}` 58 | 59 | ### `netInterceptor.removeListener(event, listener)` 60 | 61 | Remove an event listener. 62 | 63 | ### `netInterceptor.removeAllListeners(event)` 64 | 65 | Removes all event listeners for the given event. Or when called without the `event` argument, remove all listeners for all events. 66 | 67 | ### `kRemote` 68 | 69 | ```js 70 | import { kRemote } from "@gr2m/net-interceptor"; 71 | requestSocket[kRemote]; // response socket 72 | ``` 73 | 74 | `kRemote` is a symbol that can be used to access the response socket from the request socket when handling intercepted requests. 75 | 76 | ## See also 77 | 78 | - [`@gr2m/http-interceptor`](https://github.com/gr2m/node-http-interceptor) - Intercept and mock outgoing http/https requests 79 | - [`@gr2m/http-recorder`](https://github.com/gr2m/node-http-recorder) - Library agnostic in-process recording of http(s) requests and responses 80 | 81 | ## How it works 82 | 83 | Once started, `netInterceptor` hooks itself into [the `net.connect`](https://nodejs.org/api/net.html#netconnect) and [the `tls.connect`](https://nodejs.org/api/tls.html#tlsconnectoptions-callback) methods 84 | 85 | When a socket is intercepted, we 86 | 87 | 1. we create a mock net/TLS socket 88 | 2. emit the `connect` event with the mock socket 89 | 3. if `bypass()` was called in the `connect` event listener, we let the socket continue unintercepted 90 | 4. if `bypass()` was not called 91 | 1. we create another mock socket for the response and emit the "connection" event 92 | 2. we emit `connect` on both mock sockets 93 | 94 | and then emit a `record` event with the `request`, `response`, `requestBody` and `responseBody` options. 95 | 96 | ## Contributing 97 | 98 | See [CONTRIBUTING.md](CONTRIBUTING.md) 99 | 100 | ## Credits 101 | 102 | `@gr2m/net-interceptor` is built upon code and concepts from [moll/node-mitm](https://github.com/moll/node-mitm) by [Andri Möll](http://themoll.com). [Monday Calendar](https://mondayapp.com) supported that engineering work. 103 | 104 | **[Gregor Martynus](https://github.com/gr2m)** removed all `http(s)`-related code and made its focus on intercepting connections that use the lower-level `net` and `tls` modules. 105 | 106 | ## License 107 | 108 | [LGPL](LICENSE.md) 109 | -------------------------------------------------------------------------------- /lib/stream-handles.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | import stream from "node:stream"; 4 | 5 | import NODE_INTERNALS from "./node-internals.js"; 6 | 7 | const NO_ERROR_CODE = 0; 8 | const UV_EOF = NODE_INTERNALS.UV_EOF; 9 | export const kRemote = Symbol("Remote"); 10 | 11 | let uniqueId = 0; 12 | let STREAM_STATE = NODE_INTERNALS.STREAM_STATE; 13 | let STREAM_BYTES_READ = NODE_INTERNALS.STREAM_BYTES_READ; 14 | 15 | export default function createRequestResponseHandlePair() { 16 | const requestHandle = new StreamHandle(); 17 | const responseHandle = new StreamHandle(); 18 | requestHandle[kRemote] = responseHandle; 19 | responseHandle[kRemote] = requestHandle; 20 | 21 | return { requestHandle, responseHandle }; 22 | } 23 | 24 | /** 25 | * Sockets write to StreamHandle via write*String functions. The 26 | * WritableStream.prototype.write function is just used internally by 27 | * StreamHandle to queue data before pushing it to the other end via 28 | * ReadableStream.prototype.push. The receiver will then forward it to its 29 | * owner Socket via the onread property. 30 | * 31 | * StreamHandle is created for both the request side and the response side. 32 | */ 33 | class StreamHandle extends stream.Duplex { 34 | remote = null; 35 | 36 | constructor() { 37 | super(); 38 | this.id = ++uniqueId; 39 | 40 | // The "end" event follows ReadableStream.prototype.push(null). 41 | this.on("data", readData.bind(this)); 42 | this.on("end", readEof.bind(this)); 43 | 44 | // The "finish" event follows WritableStream.prototype.end. 45 | // 46 | // There's WritableStream.prototype._final for processing before "finish" is 47 | // emitted, but that's only available in Node v8 and later. 48 | this.on( 49 | "finish", 50 | this._write.bind(this, null, null, () => {}) 51 | ); 52 | 53 | this.pause(); 54 | } 55 | 56 | readStart() { 57 | this.resume(); 58 | } 59 | // readstop is not called by Node 17 60 | /* c8 ignore start */ 61 | readStop() { 62 | this.pause(); 63 | } 64 | /* c8 ignore stop */ 65 | 66 | // noops 67 | _read() {} 68 | ref() {} 69 | unref() {} 70 | 71 | getAsyncId() { 72 | return this.id; 73 | } 74 | 75 | _write(data, encoding, done) { 76 | const remote = this[kRemote]; 77 | process.nextTick(function () { 78 | remote.push(data, encoding); 79 | done(); 80 | }); 81 | } 82 | 83 | // Node requires writev to be set on the handler because, while 84 | // WritableStream expects _writev, internal/stream_base_commons.js calls 85 | // req.handle.writev directly. It's given a flat array of data+type pairs. 86 | writev(_req, data) { 87 | for (let i = 0; i < data.length; ++i) 88 | this._write(data[i], data[++i], () => {}); 89 | return NO_ERROR_CODE; 90 | } 91 | 92 | writeLatin1String(_req, data) { 93 | this.write(data, "latin1"); 94 | return NO_ERROR_CODE; 95 | } 96 | 97 | writeBuffer(request, data) { 98 | this.write(data); 99 | return NO_ERROR_CODE; 100 | } 101 | 102 | writeUtf8String(request, data) { 103 | this.write(data, "utf8"); 104 | return NO_ERROR_CODE; 105 | } 106 | 107 | writeAsciiString(request, data) { 108 | this.write(data, "ascii"); 109 | return NO_ERROR_CODE; 110 | } 111 | 112 | writeUcs2String(request, data) { 113 | this.write(data, "ucs2"); 114 | return NO_ERROR_CODE; 115 | } 116 | 117 | // While it seems to have existed since Node v0.10, Node v11.2 requires 118 | // "shutdown". AFAICT, "shutdown" is for shutting the writable side down and 119 | // hence the use of WritableStream.prototype.end and waiting for the "finish" 120 | // event. 121 | shutdown(request) { 122 | this.once( 123 | "finish", 124 | request.oncomplete.bind(request, NO_ERROR_CODE, request.handle) 125 | ); 126 | this.end(); 127 | 128 | // Must return an error code, where `1` indicating a "synchronous finish" 129 | // (as per Node's net.js) and `0` presumably success. 130 | return 0; 131 | } 132 | 133 | // Unsure of the relationship between StreamHandle.prototype.shutdown and 134 | // StreamHandle.prototype.close. 135 | close(done) { 136 | // @ts-expect-error - `._writeableState` is an internal API and not typed 137 | if (!this._writableState.finished) { 138 | this.end(done); 139 | return; 140 | } 141 | 142 | /* istanbul ignore next */ 143 | if (done) done(); 144 | } 145 | } 146 | 147 | function readData(data) { 148 | // A system written not in 1960 that passes arguments to functions through 149 | // _global_ mutable data structures… 150 | STREAM_STATE[STREAM_BYTES_READ] = data.length; 151 | this.onread(data); 152 | } 153 | 154 | function readEof() { 155 | STREAM_STATE[STREAM_BYTES_READ] = UV_EOF; 156 | this.onread(); 157 | } 158 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, caste, color, religion, or sexual 10 | identity and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | - Demonstrating empathy and kindness toward other people 21 | - Being respectful of differing opinions, viewpoints, and experiences 22 | - Giving and gracefully accepting constructive feedback 23 | - Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | - Focusing on what is best not just for us as individuals, but for the overall 26 | community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | - The use of sexualized language or imagery, and sexual attention or advances of 31 | any kind 32 | - Trolling, insulting or derogatory comments, and personal or political attacks 33 | - Public or private harassment 34 | - Publishing others' private information, such as a physical or email address, 35 | without their explicit permission 36 | - Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | [INSERT CONTACT METHOD]. 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series of 86 | actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or permanent 93 | ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within the 113 | community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.1, available at 119 | [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1]. 120 | 121 | Community Impact Guidelines were inspired by 122 | [Mozilla's code of conduct enforcement ladder][mozilla coc]. 123 | 124 | For answers to common questions about this code of conduct, see the FAQ at 125 | [https://www.contributor-covenant.org/faq][faq]. Translations are available at 126 | [https://www.contributor-covenant.org/translations][translations]. 127 | 128 | [homepage]: https://www.contributor-covenant.org 129 | [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html 130 | [mozilla coc]: https://github.com/mozilla/diversity 131 | [faq]: https://www.contributor-covenant.org/faq 132 | [translations]: https://www.contributor-covenant.org/translations 133 | -------------------------------------------------------------------------------- /test/net-test.js: -------------------------------------------------------------------------------- 1 | import net from "node:net"; 2 | import tls from "node:tls"; 3 | import stream from "node:stream"; 4 | 5 | import sinon from "sinon"; 6 | import { test } from "uvu"; 7 | import * as assert from "uvu/assert"; 8 | 9 | import netInterceptor from "../index.js"; 10 | 11 | test.before.each(() => { 12 | netInterceptor.start(); 13 | }); 14 | test.after.each(() => { 15 | netInterceptor.removeAllListeners(); 16 | sinon.restore(); 17 | }); 18 | 19 | test(`does not emit "connect" when stopped`, () => { 20 | netInterceptor.stop(); 21 | const onConnect = sinon.spy(); 22 | const onError = sinon.spy(); 23 | netInterceptor.on("connect", onConnect); 24 | const socket = net.connect({ port: 80 }); 25 | socket.on("error", onError); 26 | 27 | socket.on("close", () => { 28 | assert.equal(onConnect.callCount, 0); 29 | assert.equal(onError.callCount, 1); 30 | }); 31 | }); 32 | 33 | function mustConnect(moduleName, module) { 34 | test(`${moduleName}: must return an instance of net.Socket`, () => { 35 | const socket = module.connect({ host: "foo", port: 80 }); 36 | assert.instance(socket, net.Socket); 37 | }); 38 | 39 | test(`${moduleName}: must return an instance of net.Socket given port`, () => { 40 | assert.instance(module.connect(80), net.Socket); 41 | }); 42 | 43 | test(`${moduleName}: must return an instance of net.Socket given port and host`, () => { 44 | assert.instance(module.connect(80, "10.0.0.1"), net.Socket); 45 | }); 46 | 47 | test(`${moduleName}: must emit connect on netInterceptor`, () => { 48 | const onConnect = sinon.spy(); 49 | netInterceptor.on("connect", onConnect); 50 | const opts = { host: "foo" }; 51 | const socket = module.connect(opts); 52 | 53 | assert.equal(onConnect.callCount, 1); 54 | assert.equal(onConnect.firstCall.args[0], socket); 55 | assert.equal(onConnect.firstCall.args[1], opts); 56 | }); 57 | 58 | test(`${moduleName}: must emit connect on netInterceptor with options object given host and port`, () => { 59 | const onConnect = sinon.spy(); 60 | netInterceptor.on("connect", onConnect); 61 | const socket = module.connect(9, "127.0.0.1"); 62 | 63 | assert.equal(onConnect.callCount, 1); 64 | assert.equal(onConnect.firstCall.args[0], socket); 65 | assert.equal(onConnect.firstCall.args[1], { host: "127.0.0.1", port: 9 }); 66 | }); 67 | 68 | test(`${moduleName}: must emit connection on netInterceptor`, () => { 69 | const onConnection = sinon.spy(); 70 | netInterceptor.on("connection", onConnection); 71 | const opts = { host: "foo" }; 72 | const socket = module.connect(opts); 73 | 74 | assert.equal(onConnection.callCount, 1); 75 | assert.instance(onConnection.firstCall.args[0], net.Socket); 76 | assert.not.equal(onConnection.firstCall.args[0], socket); 77 | assert.equal(onConnection.firstCall.args[1], opts); 78 | }); 79 | 80 | test(`${moduleName}: must emit connect on socket in next ticks`, () => { 81 | return new Promise((resolve) => { 82 | const socket = module.connect({ host: "foo" }); 83 | socket.on("connect", resolve); 84 | }); 85 | }); 86 | 87 | test(`${moduleName}: must call back on connect given callback`, () => { 88 | return new Promise((resolve) => { 89 | module.connect({ host: "foo" }, resolve); 90 | }); 91 | }); 92 | 93 | test(`${moduleName}: must call back on connect given port and callback`, () => { 94 | return new Promise((resolve) => { 95 | module.connect(80, resolve); 96 | }); 97 | }); 98 | 99 | // This was a bug found on Apr 26, 2014 where the host argument was taken 100 | // to be the callback because arguments weren't normalized to an options 101 | // object. 102 | test(`${moduleName}: must call back on connect given port, host and callback`, () => { 103 | return new Promise((resolve) => { 104 | module.connect(80, "localhost", resolve); 105 | }); 106 | }); 107 | 108 | // The "close" event broke on Node v12.16.3 as the 109 | // InternalSocket.prototype.close method didn't call back if 110 | // the WritableStream had already been closed. 111 | test(`${moduleName}: must emit close on socket if ended immediately`, () => { 112 | return new Promise((resolve) => { 113 | netInterceptor.on("connection", (socket) => socket.end()); 114 | const socket = module.connect({ host: "foo" }); 115 | socket.on("close", resolve); 116 | }); 117 | }); 118 | 119 | test(`${moduleName}: must emit close on socket if ended in next tick`, () => { 120 | return new Promise((resolve) => { 121 | netInterceptor.on("connection", (socket) => 122 | process.nextTick(socket.end.bind(socket)) 123 | ); 124 | 125 | const socket = module.connect({ host: "foo" }); 126 | socket.on("close", resolve); 127 | }); 128 | }); 129 | 130 | test(`${moduleName}: must intercept 127.0.0.1`, () => { 131 | return new Promise((resolve) => { 132 | let server; 133 | netInterceptor.on("connection", (socket) => (server = socket)); 134 | const client = module.connect({ host: "127.0.0.1" }); 135 | server.write("Hello"); 136 | 137 | client.setEncoding("utf8"); 138 | client.on("data", (data) => assert.equal(data, "Hello")); 139 | client.on("data", resolve); 140 | }); 141 | }); 142 | 143 | test(`${moduleName}: when bypassed must not intercept`, () => { 144 | return new Promise((resolve) => { 145 | netInterceptor.on("connect", (client, options, bypass) => bypass()); 146 | 147 | module.connect({ host: "127.0.0.1", port: 9 }).on("error", (error) => { 148 | assert.instance(error, Error); 149 | assert.match(error.message, /ECONNREFUSED/); 150 | resolve(); 151 | }); 152 | }); 153 | }); 154 | 155 | test(`${moduleName}: when bypassed must call original module.connect`, () => { 156 | const connect = sinon.spy(module, "connect"); 157 | netInterceptor.on("connect", (client, options, bypass) => bypass()); 158 | 159 | module.connect({ host: "127.0.0.1", port: 9 }).on("error", noop); 160 | assert.equal(connect.callCount, 1); 161 | assert.equal(connect.firstCall.args[0], { host: "127.0.0.1", port: 9 }); 162 | }); 163 | 164 | test(`${moduleName}: when bypassed must not call back twice on connect given callback`, () => { 165 | return new Promise((resolve) => { 166 | netInterceptor.on("connect", (client, options, bypass) => bypass()); 167 | 168 | const onConnect = sinon.spy(); 169 | const client = module.connect({ host: "127.0.0.1", port: 9 }, onConnect); 170 | 171 | client.on( 172 | "error", 173 | process.nextTick.bind(null, () => { 174 | assert.equal(onConnect.callCount, 0); 175 | resolve(); 176 | }) 177 | ); 178 | }); 179 | }); 180 | 181 | test(`${moduleName}: when bypassed must not emit connection`, () => { 182 | netInterceptor.on("connect", (client, options, bypass) => bypass()); 183 | const onConnection = sinon.spy(); 184 | netInterceptor.on("connection", onConnection); 185 | module.connect({ host: "127.0.0.1", port: 9 }).on("error", noop); 186 | assert.equal(onConnection.callCount, 0); 187 | }); 188 | } 189 | 190 | mustConnect("net.connect", net); 191 | 192 | test("net.connect must not return an instance of tls.TLSSocket", () => { 193 | const client = net.connect({ host: "foo", port: 80 }); 194 | // we don't use `instanceof(tls.TLSSocket)` here because our TlsSocket 195 | // implementation doesn't extend tls.TLSSocket. 196 | assert.not.ok("getCertificate" in client); 197 | }); 198 | 199 | test("net.connect must not set the encrypted property", () => { 200 | assert.not.ok("encrypted" in net.connect({ host: "foo" })); 201 | }); 202 | 203 | test("net.connect must not set the authorized property", () => { 204 | assert.not.ok("authorized" in net.connect({ host: "foo" })); 205 | }); 206 | 207 | test("net.connect must not emit secureConnect on client", () => { 208 | return new Promise((resolve) => { 209 | const client = net.connect({ host: "foo" }); 210 | client.on("secureConnect", resolve); 211 | resolve(); 212 | }); 213 | }); 214 | 215 | test("net.connect must not emit secureConnect on server", () => { 216 | return new Promise((resolve) => { 217 | let server; 218 | netInterceptor.on("connection", (socket) => (server = socket)); 219 | net.connect({ host: "foo" }); 220 | server.on("secureConnect", resolve); 221 | resolve(); 222 | }); 223 | }); 224 | 225 | test("Socket.prototype.write must write to client from server", () => { 226 | return new Promise((resolve) => { 227 | let server; 228 | netInterceptor.on("connection", (socket) => (server = socket)); 229 | const client = net.connect({ host: "foo" }); 230 | server.write("Hello ☺️"); 231 | 232 | client.setEncoding("utf8"); 233 | client.on("data", (data) => assert.equal(data, "Hello ☺️")); 234 | client.on("data", resolve); 235 | }); 236 | }); 237 | 238 | test("Socket.prototype.write must write to client from server in the next tick", () => { 239 | return new Promise((resolve) => { 240 | let server; 241 | netInterceptor.on("connection", (socket) => (server = socket)); 242 | const client = net.connect({ host: "foo" }); 243 | 244 | let ticked = false; 245 | client.once("data", () => { 246 | assert.equal(ticked, true); 247 | resolve(); 248 | }); 249 | server.write("Hello"); 250 | ticked = true; 251 | }); 252 | }); 253 | 254 | test("Socket.prototype.write must write to server from client", () => { 255 | return new Promise((resolve) => { 256 | let server; 257 | netInterceptor.on("connection", (socket) => (server = socket)); 258 | const client = net.connect({ host: "foo" }); 259 | client.write("Hello ☺️"); 260 | 261 | server.setEncoding("utf8"); 262 | process.nextTick(() => { 263 | assert.equal(server.read(), "Hello ☺️"); 264 | resolve(); 265 | }); 266 | }); 267 | }); 268 | 269 | test("Socket.prototype.write must write to server from client in the next tick", () => { 270 | return new Promise((resolve) => { 271 | let server; 272 | netInterceptor.on("connection", (socket) => (server = socket)); 273 | const client = net.connect({ host: "foo" }); 274 | 275 | let ticked = false; 276 | server.once("data", () => { 277 | assert.equal(ticked, true); 278 | resolve(); 279 | }); 280 | client.write("Hello"); 281 | ticked = true; 282 | }); 283 | }); 284 | 285 | test("Socket.prototype.write writes to buffer and flushes when uncorked", () => { 286 | return new Promise((resolve) => { 287 | let server; 288 | netInterceptor.on("connection", (socket) => (server = socket)); 289 | const client = net.connect({ host: "foo" }); 290 | 291 | let dataCount = 0; 292 | server.on("data", (data) => { 293 | dataCount += 1; 294 | if (data.toString() === "2") { 295 | assert.equal(dataCount, 2); 296 | resolve(); 297 | } 298 | }); 299 | client.cork(); 300 | client.write("1"); 301 | client.write("2"); 302 | client.uncork(); 303 | }); 304 | }); 305 | 306 | // Writing binary strings was introduced in Node v0.11.14. 307 | // The test still passes for Node v0.10 and newer v0.11s, so let it be. 308 | test("Socket.prototype.write must write to server from client given binary", () => { 309 | return new Promise((resolve) => { 310 | let server; 311 | netInterceptor.on("connection", (socket) => (server = socket)); 312 | const client = net.connect({ host: "foo" }); 313 | client.write("Hello", "utf-8"); 314 | 315 | server.setEncoding("binary"); 316 | process.nextTick(() => { 317 | assert.equal(server.read(), "Hello"); 318 | }); 319 | process.nextTick(resolve); 320 | }); 321 | }); 322 | 323 | test("Socket.prototype.write must write to server from client given latin1", () => { 324 | return new Promise((resolve) => { 325 | let server; 326 | netInterceptor.on("connection", (socket) => (server = socket)); 327 | const client = net.connect({ host: "foo" }); 328 | client.write("Hello", "latin1"); 329 | 330 | server.setEncoding("latin1"); 331 | process.nextTick(() => { 332 | assert.equal(server.read(), "Hello"); 333 | }); 334 | process.nextTick(resolve); 335 | }); 336 | }); 337 | 338 | test("Socket.prototype.write must write to server from client given a buffer", () => { 339 | return new Promise((resolve) => { 340 | let server; 341 | netInterceptor.on("connection", (socket) => (server = socket)); 342 | const client = net.connect({ host: "foo" }); 343 | client.write(Buffer.from("Hello", "utf-8")); 344 | 345 | process.nextTick(() => { 346 | assert.equal(server.read(), Buffer.from("Hello", "utf-8")); 347 | resolve(); 348 | }); 349 | }); 350 | }); 351 | 352 | test("Socket.prototype.write must write to server from client given a UTF-8 string", () => { 353 | return new Promise((resolve) => { 354 | let server; 355 | netInterceptor.on("connection", (socket) => (server = socket)); 356 | const client = net.connect({ host: "foo" }); 357 | client.write("Hello", "utf8"); 358 | 359 | process.nextTick(() => { 360 | assert.equal(server.read(), Buffer.from("Hello", "utf-8")); 361 | resolve(); 362 | }); 363 | }); 364 | }); 365 | 366 | test("Socket.prototype.write must write to server from client given a ASCII string", () => { 367 | return new Promise((resolve) => { 368 | let server; 369 | netInterceptor.on("connection", (socket) => (server = socket)); 370 | const client = net.connect({ host: "foo" }); 371 | client.write("Hello", "ascii"); 372 | 373 | process.nextTick(() => { 374 | assert.equal(server.read(), Buffer.from("Hello", "utf-8")); 375 | resolve(); 376 | }); 377 | }); 378 | }); 379 | 380 | test("Socket.prototype.write must write to server from client given a UCS-2 string", () => { 381 | return new Promise((resolve) => { 382 | let server; 383 | netInterceptor.on("connection", (socket) => (server = socket)); 384 | const client = net.connect({ host: "foo" }); 385 | client.write("Hello", "ucs2"); 386 | 387 | process.nextTick(() => { 388 | assert.equal( 389 | server.read(), 390 | Buffer.from("H\u0000e\u0000l\u0000l\u0000o\u0000", "utf-8") 391 | ); 392 | 393 | resolve(); 394 | }); 395 | }); 396 | }); 397 | 398 | test("Socket.prototype.end() must emit end when closed on server", () => { 399 | return new Promise((resolve) => { 400 | let server; 401 | netInterceptor.on("connection", (socket) => (server = socket)); 402 | const client = net.connect({ host: "foo" }); 403 | server.end(); 404 | client.on("end", resolve); 405 | }); 406 | }); 407 | 408 | test("Socket.prototype.ref must allow calling on client", () => { 409 | net.connect({ host: "foo" }).ref(); 410 | }); 411 | 412 | test("Socket.prototype.ref must allow calling on server", () => { 413 | let server; 414 | netInterceptor.on("connection", (socket) => (server = socket)); 415 | net.connect({ host: "foo" }); 416 | server.ref(); 417 | }); 418 | 419 | test("Socket.prototype.unref must allow calling on client", () => { 420 | net.connect({ host: "foo" }).unref(); 421 | }); 422 | 423 | test("Socket.prototype.unref must allow calling on server", () => { 424 | let server; 425 | netInterceptor.on("connection", (socket) => (server = socket)); 426 | net.connect({ host: "foo" }); 427 | server.unref(); 428 | }); 429 | 430 | // To confirm https://github.com/moll/node-netInterceptor/issues/47 won't become 431 | // an issue. 432 | test("Socket.prototype.pipe must allow piping to itself", () => { 433 | return new Promise((resolve) => { 434 | netInterceptor.on("connection", (server) => { 435 | server.pipe(new Upcase()).pipe(server); 436 | }); 437 | 438 | const client = net.connect({ host: "foo" }); 439 | client.write("Hello"); 440 | 441 | client.setEncoding("utf8"); 442 | client.on("data", (data) => assert.equal(data, "HELLO")); 443 | client.on("data", resolve); 444 | }); 445 | }); 446 | 447 | // Bug report from Io.js v3 days: 448 | // https://github.com/moll/node-netInterceptor/issues/26 449 | test("Socket.prototype.destroy must emit end when destroyed on server", () => { 450 | return new Promise((resolve) => { 451 | let server; 452 | netInterceptor.on("connection", (socket) => (server = socket)); 453 | const client = net.connect({ host: "foo" }); 454 | server.destroy(); 455 | client.on("end", resolve); 456 | }); 457 | }); 458 | 459 | test("net.createConnection must be equal to net.connect", () => { 460 | assert.equal(net.createConnection, net.connect); 461 | }); 462 | 463 | mustConnect("tls.connect", tls); 464 | 465 | test("tls.connect must return an instance of tls.TLSSocket", () => { 466 | // we don't use `instanceof(tls.TLSSocket)` here because our TlsSocket 467 | // implementation doesn't extend tls.TLSSocket. 468 | assert.ok("getCertificate" in tls.connect({ host: "foo", port: 80 })); 469 | }); 470 | 471 | test("tls.connect must return an instance of tls.TLSSocket given port", () => { 472 | // we don't use `instanceof(tls.TLSSocket)` here because our TlsSocket 473 | // implementation doesn't extend tls.TLSSocket. 474 | assert.ok("getCertificate" in tls.connect(80)); 475 | }); 476 | 477 | test("tls.connect must return an instance of tls.TLSSocket given port and host", () => { 478 | // we don't use `instanceof(tls.TLSSocket)` here because our TlsSocket 479 | // implementation doesn't extend tls.TLSSocket. 480 | assert.ok("getCertificate" in tls.connect(80, "10.0.0.1")); 481 | }); 482 | 483 | test("tls.connect must emit secureConnect in next ticks", () => { 484 | return new Promise((resolve) => { 485 | const socket = tls.connect({ host: "foo" }); 486 | socket.on("secureConnect", resolve); 487 | }); 488 | }); 489 | 490 | test("tls.connect must emit secureConnect after connect in next ticks", () => { 491 | return new Promise((resolve) => { 492 | const socket = tls.connect({ host: "foo" }); 493 | 494 | socket.on("connect", () => { 495 | socket.on("secureConnect", resolve); 496 | }); 497 | }); 498 | }); 499 | 500 | test("tls.connect must not emit secureConnect on server", () => { 501 | return new Promise((resolve) => { 502 | let server; 503 | netInterceptor.on("connection", (socket) => (server = socket)); 504 | tls.connect({ host: "foo" }); 505 | server.on("secureConnect", resolve); 506 | resolve(); 507 | }); 508 | }); 509 | 510 | test("tls.connect must call back on secureConnect", () => { 511 | return new Promise((resolve) => { 512 | let connected = false; 513 | 514 | const client = tls.connect({ host: "foo" }, () => { 515 | assert.equal(connected, true); 516 | resolve(); 517 | }); 518 | 519 | client.on("connect", () => { 520 | connected = true; 521 | }); 522 | }); 523 | }); 524 | 525 | test("tls.connect must set encrypted true", () => { 526 | assert.equal(tls.connect({ host: "foo" }).encrypted, true); 527 | }); 528 | 529 | test("tls.connect must set authorized true", () => { 530 | assert.equal(tls.connect({ host: "foo" }).authorized, true); 531 | }); 532 | 533 | test.run(); 534 | 535 | function Upcase() { 536 | stream.Transform.call(this, arguments); 537 | } 538 | 539 | Upcase.prototype = Object.create(stream.Transform.prototype, { 540 | constructor: { value: Upcase, configurable: true, writeable: true }, 541 | }); 542 | 543 | Upcase.prototype._transform = (chunk, _enc, done) => { 544 | done(null, String(chunk).toUpperCase()); 545 | }; 546 | 547 | function noop() {} 548 | --------------------------------------------------------------------------------