import { EventEmitter } from 'events'; import fs from 'fs'; // Note: we cannot import these from `node-libcurl` like normal because they come from the native library and it's not possible to load it while testing because it was built to run with Electron. // That applies to these Enum type imports, but also applies to the members of the class below. import { CurlAuth } from 'node-libcurl/dist/enum/CurlAuth'; import { CurlCode } from 'node-libcurl/dist/enum/CurlCode'; import { CurlFeature } from 'node-libcurl/dist/enum/CurlFeature'; import { CurlHttpVersion } from 'node-libcurl/dist/enum/CurlHttpVersion'; import { CurlInfoDebug } from 'node-libcurl/dist/enum/CurlInfoDebug'; import { CurlNetrc } from 'node-libcurl/dist/enum/CurlNetrc'; class Curl extends EventEmitter { _options = {}; _meta = {}; _features = {}; static info = { COOKIELIST: 'COOKIELIST', EFFECTIVE_URL: 'EFFECTIVE_URL', SIZE_DOWNLOAD: 'SIZE_DOWNLOAD', TOTAL_TIME: 'TOTAL_TIME', } static option = { ACCEPT_ENCODING: 'ACCEPT_ENCODING', CAINFO: 'CAINFO', COOKIEFILE: 'COOKIEFILE', COOKIELIST: 'COOKIELIST', CUSTOMREQUEST: 'CUSTOMREQUEST', DEBUGFUNCTION: 'DEBUGFUNCTION', FOLLOWLOCATION: 'FOLLOWLOCATION', HTTPAUTH: 'HTTPAUTH', HTTPGET: 'HTTPGET', HTTPHEADER: 'HTTPHEADER', HTTPPOST: 'HTTPPOST', HTTP_VERSION: 'HTTP_VERSION', INFILESIZE_LARGE: 'INFILESIZE_LARGE', KEYPASSWD: 'KEYPASSWD', MAXREDIRS: 'MAXREDIRS', NETRC: 'NETRC', NOBODY: 'NOBODY', NOPROGRESS: 'NOPROGRESS', NOPROXY: 'NOPROXY', PASSWORD: 'PASSWORD', POST: 'POST', POSTFIELDS: 'POSTFIELDS', PROXY: 'PROXY', PROXYAUTH: 'PROXYAUTH', READDATA: 'READDATA', READFUNCTION: 'READFUNCTION', SSLCERT: 'SSLCERT', SSLCERTTYPE: 'SSLCERTTYPE', SSLKEY: 'SSLKEY', SSL_VERIFYHOST: 'SSL_VERIFYHOST', SSL_VERIFYPEER: 'SSL_VERIFYPEER', TIMEOUT_MS: 'TIMEOUT_MS', UNIX_SOCKET_PATH: 'UNIX_SOCKET_PATH', UPLOAD: 'UPLOAD', URL: 'URL', USERAGENT: 'USERAGENT', USERNAME: 'USERNAME', VERBOSE: 'VERBOSE', WRITEFUNCTION: 'WRITEFUNCTION', XFERINFOFUNCTION: 'XFERINFOFUNCTION', } static getVersion() { return 'libcurl/7.54.0 LibreSSL/2.0.20 zlib/1.2.11 nghttp2/1.24.0'; } enable(name) { this._features[name] = true; } setOpt(name, value) { if (!name) { throw new Error(`Invalid option ${name} ${value}`); } if (name === Curl.option.CAINFO) { // Just ignore this because it's platform-specific return; } if (name === Curl.option.READFUNCTION) { let body = ''; // Only limiting this to prevent infinite loops for (let i = 0; i < 1000; i++) { const buffer = Buffer.alloc(23); const bytes = value(buffer); if (bytes === 0) { break; } body += buffer.slice(0, bytes); } this._meta[`${name}_VALUE`] = body; } if (name === Curl.option.COOKIELIST) { // This can be set multiple times this._options[name] = this._options[name] || []; this._options[name].push(value); } else if (name === Curl.option.READDATA) { const { size } = fs.fstatSync(value); const buffer = Buffer.alloc(size); fs.readSync(value, buffer, 0, size, 0); this._options[name] = buffer.toString(); } else { this._options[name] = value; } } getInfo(name) { switch (name) { case Curl.info.COOKIELIST: return [`#HttpOnly_.insomnia.rest\tTRUE\t/url/path\tTRUE\t${Date.now() / 1000}\tfoo\tbar`]; case Curl.info.EFFECTIVE_URL: return this._options[Curl.option.URL]; case Curl.info.TOTAL_TIME: return 700; case Curl.info.SIZE_DOWNLOAD: return 800; default: throw new Error(`Invalid info ${name}`); } } perform() { process.nextTick(() => { const data = Buffer.from( JSON.stringify({ options: this._options, meta: this._meta, features: this._features, }), ); this.emit('data', data); // @ts-expect-error -- TSCONVERSION this._options.WRITEFUNCTION(data); process.nextTick(() => { this.emit( 'end', 'NOT_USED', 'NOT_USED', [ 'HTTP/1.1 200 OK', `Content-Length: ${data.length}`, 'Content-Type: application/json', '', ].join('\n'), ); }); }); } close() {} } /** * This is just to make it easier to test * node-libcurl Enum exports (CurlAuth, CurlCode, etc) are TypeScript enums, which are converted to an object with format: * ```ts * const myEnum = { * EnumKey: 0, * 0: EnumKey, * } * ``` * We only want the named members (non-number ones) */ const getTsEnumOnlyWithNamedMembers = enumObj => { let obj = {}; for (const member in enumObj) { if (typeof enumObj[member] === 'number') { obj = { ...obj, [member]: member }; } } return obj; }; // WARNING: changing this to `export default` will break the mock and be incredibly hard to debug. Ask me how I know. module.exports = { Curl, CurlAuth: getTsEnumOnlyWithNamedMembers(CurlAuth), CurlCode: getTsEnumOnlyWithNamedMembers(CurlCode), CurlInfoDebug: getTsEnumOnlyWithNamedMembers(CurlInfoDebug), CurlFeature: getTsEnumOnlyWithNamedMembers(CurlFeature), CurlNetrc: getTsEnumOnlyWithNamedMembers(CurlNetrc), CurlHttpVersion: getTsEnumOnlyWithNamedMembers(CurlHttpVersion), };