UNPKG

node-libcurl

Version:

The fastest http(s) client (and much more) for Node.js - Node.js bindings for libcurl

448 lines 19.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.Curl = void 0; const tslib_1 = require("tslib"); /** * Copyright (c) Jonathan Cardoso Machado. All Rights Reserved. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ const events_1 = require("events"); const string_decoder_1 = require("string_decoder"); const assert_1 = tslib_1.__importDefault(require("assert")); const pkg = require('../package.json'); const Easy_1 = require("./Easy"); const Multi_1 = require("./Multi"); const mergeChunks_1 = require("./mergeChunks"); const parseHeaders_1 = require("./parseHeaders"); const CurlCode_1 = require("./enum/CurlCode"); const CurlFeature_1 = require("./enum/CurlFeature"); const bindings = require('../lib/binding/node_libcurl.node'); // tslint:disable-next-line const { Curl: _Curl, CurlVersionInfo } = bindings; if (!process.env.NODE_LIBCURL_DISABLE_GLOBAL_INIT_CALL || process.env.NODE_LIBCURL_DISABLE_GLOBAL_INIT_CALL !== 'true') { // We could just pass nothing here, CurlGlobalInitEnum.All is the default anyway. const globalInitResult = _Curl.globalInit(3 /* All */); assert_1.default(globalInitResult === 0 || 'Libcurl global init failed.'); } const decoder = new string_decoder_1.StringDecoder('utf8'); // Handle used by curl instances created by the Curl wrapper. const multiHandle = new Multi_1.Multi(); const curlInstanceMap = new WeakMap(); multiHandle.onMessage((error, handle, errorCode) => { multiHandle.removeHandle(handle); const curlInstance = curlInstanceMap.get(handle); assert_1.default(curlInstance, 'Could not retrieve curl instance from easy handle on onMessage callback'); if (error) { curlInstance.onError(error, errorCode); } else { curlInstance.onEnd(); } }); /** * Wrapper around {@link "Easy".Easy | `Easy`} class with a more *nodejs-friendly* interface. * * This uses an internal {@link "Multi".Multi | `Multi`} instance to asynchronous fire the requests. * * @public */ let Curl = /** @class */ (() => { class Curl extends events_1.EventEmitter { /** * @param cloneHandle {@link "Easy".Easy | `Easy`} handle that should be used instead of creating a new one. */ constructor(cloneHandle) { super(); const handle = cloneHandle || new Easy_1.Easy(); this.handle = handle; // callbacks called by libcurl handle.setOpt(Curl.option.WRITEFUNCTION, this.defaultWriteFunction.bind(this)); handle.setOpt(Curl.option.HEADERFUNCTION, this.defaultHeaderFunction.bind(this)); handle.setOpt(Curl.option.USERAGENT, Curl.defaultUserAgent); this.chunks = []; this.chunksLength = 0; this.headerChunks = []; this.headerChunksLength = 0; this.features = 0; this.isRunning = false; curlInstanceMap.set(handle, this); } /** * This is the default callback passed to {@link setOpt | `setOpt('WRITEFUNCTION', cb)`}. */ defaultWriteFunction(chunk, size, nmemb) { if (!(this.features & CurlFeature_1.CurlFeature.NoDataStorage)) { this.chunks.push(chunk); this.chunksLength += chunk.length; } this.emit('data', chunk, this); return size * nmemb; } /** * This is the default callback passed to {@link setOpt | `setOpt('HEADERFUNCTION', cb)`}. */ defaultHeaderFunction(chunk, size, nmemb) { if (!(this.features & CurlFeature_1.CurlFeature.NoHeaderStorage)) { this.headerChunks.push(chunk); this.headerChunksLength += chunk.length; } this.emit('header', chunk, this); return size * nmemb; } /** * Callback called when an error is thrown on this handle. * * This is called from the internal callback we use with the {@link "Multi".Multi.onMessage | `onMessage`} * method of the global {@link "Multi".Multi | `Multi`} handle used by all `Curl` instances. */ onError(error, errorCode) { this.isRunning = false; this.chunks = []; this.chunksLength = 0; this.headerChunks = []; this.headerChunksLength = 0; this.emit('error', error, errorCode, this); } /** * Callback called when this handle has finished the request. * * This is called from the internal callback we use with the {@link "Multi".Multi.onMessage | `onMessage`} * method of the global {@link "Multi".Multi | `Multi`} handle used by all `Curl` instances. */ onEnd() { const isHeaderStorageEnabled = !(this.features & CurlFeature_1.CurlFeature.NoHeaderStorage); const isDataStorageEnabled = !(this.features & CurlFeature_1.CurlFeature.NoDataStorage); const isHeaderParsingEnabled = !(this.features & CurlFeature_1.CurlFeature.NoHeaderParsing) && isHeaderStorageEnabled; const isDataParsingEnabled = !(this.features & CurlFeature_1.CurlFeature.NoDataParsing) && isDataStorageEnabled; this.isRunning = false; const dataRaw = isDataStorageEnabled ? mergeChunks_1.mergeChunks(this.chunks, this.chunksLength) : Buffer.alloc(0); const headersRaw = isHeaderStorageEnabled ? mergeChunks_1.mergeChunks(this.headerChunks, this.headerChunksLength) : Buffer.alloc(0); this.chunks = []; this.chunksLength = 0; this.headerChunks = []; this.headerChunksLength = 0; const data = isDataParsingEnabled ? decoder.write(dataRaw) : dataRaw; const headers = isHeaderParsingEnabled ? parseHeaders_1.parseHeaders(decoder.write(headersRaw)) : headersRaw; const { code, data: status } = this.handle.getInfo(Curl.info.RESPONSE_CODE); if (code !== CurlCode_1.CurlCode.CURLE_OK) { const error = new Error('Could not get status code of request'); this.emit('error', error, code, this); } else { this.emit('end', status, data, headers, this); } } /** * Enables a feature, must not be used while a request is running. * * Use {@link CurlFeature | `CurlFeature`} for predefined constants. */ enable(bitmask) { if (this.isRunning) { throw new Error('You should not change the features while a request is running.'); } this.features |= bitmask; return this; } /** * Disables a feature, must not be used while a request is running. * * Use {@link CurlFeature | `CurlFeature`} for predefined constants. */ disable(bitmask) { if (this.isRunning) { throw new Error('You should not change the features while a request is running.'); } this.features &= ~bitmask; return this; } /** * Sets an option the handle. * * This overloaded method has `never` as type for the arguments * because one of the other overloaded signatures must be used. * * * Official libcurl documentation: [`curl_easy_setopt()`](http://curl.haxx.se/libcurl/c/curl_easy_setopt.html) * * @param optionIdOrName Option name or integer value. Use {@link Curl.option | `Curl.option`} for predefined constants. * @param optionValue The value of the option, value type depends on the option being set. */ setOpt(optionIdOrName, optionValue) { // special case for WRITEFUNCTION and HEADERFUNCTION callbacks // since if they are set back to null, we must restore the default callback. let value = optionValue; if ((optionIdOrName === Curl.option.WRITEFUNCTION || optionIdOrName === 'WRITEFUNCTION') && !optionValue) { value = this.defaultWriteFunction.bind(this); } else if ((optionIdOrName === Curl.option.HEADERFUNCTION || optionIdOrName === 'HEADERFUNCTION') && !optionValue) { value = this.defaultHeaderFunction.bind(this); } const code = this.handle.setOpt(optionIdOrName, value); if (code !== CurlCode_1.CurlCode.CURLE_OK) { throw new Error(code === CurlCode_1.CurlCode.CURLE_UNKNOWN_OPTION ? 'Unknown option given. First argument must be the option internal id or the option name. You can use the Curl.option constants.' : Easy_1.Easy.strError(code)); } return this; } /** * Retrieves some information about the last request made by a handle. * * * Official libcurl documentation: [`curl_easy_getinfo()`](http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html) * * @param infoNameOrId Info name or integer value. Use {@link Curl.info | `Curl.info`} for predefined constants. */ getInfo(infoNameOrId) { const { code, data } = this.handle.getInfo(infoNameOrId); if (code !== CurlCode_1.CurlCode.CURLE_OK) { throw new Error(`getInfo failed. Error: ${Easy_1.Easy.strError(code)}`); } return data; } /** * The option `XFERINFOFUNCTION` was introduced in curl version `7.32.0`, * versions older than that should use `PROGRESSFUNCTION`. * If you don't want to mess with version numbers you can use this method, * instead of directly calling {@link Curl.setOpt | `Curl#setOpt`}. * * `NOPROGRESS` should be set to false to make this function actually get called. */ setProgressCallback(cb) { if (Curl.VERSION_NUM >= 0x072000) { this.handle.setOpt(Curl.option.XFERINFOFUNCTION, cb); } else { this.handle.setOpt(Curl.option.PROGRESSFUNCTION, cb); } return this; } /** * Add this instance to the processing queue. * This method should be called only one time per request, * otherwise it will throw an error. * * @remarks * * This basically calls the {@link "Multi".Multi.addHandle | `Multi#addHandle`} method. */ perform() { if (this.isRunning) { throw new Error('Handle already running!'); } this.isRunning = true; multiHandle.addHandle(this.handle); return this; } /** * Perform any connection upkeep checks. * * * Official libcurl documentation: [`curl_easy_upkeep()`](http://curl.haxx.se/libcurl/c/curl_easy_upkeep.html) */ upkeep() { const code = this.handle.upkeep(); if (code !== CurlCode_1.CurlCode.CURLE_OK) { throw new Error(Easy_1.Easy.strError(code)); } return this; } /** * Use this function to pause / unpause a connection. * * The bitmask argument is a set of bits that sets the new state of the connection. * * Use {@link CurlPause | `CurlPause`} for predefined constants. * * * Official libcurl documentation: [`curl_easy_pause()`](http://curl.haxx.se/libcurl/c/curl_easy_pause.html) */ pause(bitmask) { const code = this.handle.pause(bitmask); if (code !== CurlCode_1.CurlCode.CURLE_OK) { throw new Error(Easy_1.Easy.strError(code)); } return this; } /** * Reset this handle options to their defaults. * * This will put the handle in a clean state, as if it was just created. * * * Official libcurl documentation: [`curl_easy_reset()`](http://curl.haxx.se/libcurl/c/curl_easy_reset.html) */ reset() { this.removeAllListeners(); this.handle.reset(); // add callbacks back as reset will remove them this.handle.setOpt(Curl.option.WRITEFUNCTION, this.defaultWriteFunction.bind(this)); this.handle.setOpt(Curl.option.HEADERFUNCTION, this.defaultHeaderFunction.bind(this)); return this; } /** * Duplicate this handle with all their options. * Keep in mind that, by default, this also means all event listeners. * * * Official libcurl documentation: [`curl_easy_duphandle()`](http://curl.haxx.se/libcurl/c/curl_easy_duphandle.html) * * @param shouldCopyEventListeners If you don't want to copy the event listeners, set this to `false`. */ dupHandle(shouldCopyEventListeners = true) { const duplicatedHandle = new Curl(this.handle.dupHandle()); const eventsToCopy = ['end', 'error', 'data', 'header']; duplicatedHandle.features = this.features; if (shouldCopyEventListeners) { for (let i = 0; i < eventsToCopy.length; i += 1) { const listeners = this.listeners(eventsToCopy[i]); for (let j = 0; j < listeners.length; j += 1) { duplicatedHandle.on(eventsToCopy[i], listeners[j]); } } } return duplicatedHandle; } /** * Close this handle. * * **NOTE:** After closing the handle, it must not be used anymore. Doing so will throw an error. * * * Official libcurl documentation: [`curl_easy_cleanup()`](http://curl.haxx.se/libcurl/c/curl_easy_cleanup.html) */ close() { curlInstanceMap.delete(this.handle); this.removeAllListeners(); if (this.handle.isInsideMultiHandle) { multiHandle.removeHandle(this.handle); } this.handle.setOpt(Curl.option.WRITEFUNCTION, null); this.handle.setOpt(Curl.option.HEADERFUNCTION, null); this.handle.close(); } } /** * Calls [`curl_global_init()`](http://curl.haxx.se/libcurl/c/curl_global_init.html). * * For **flags** see the the enum {@link CurlGlobalInit | `CurlGlobalInit`}. * * This is automatically called when the addon is loaded, to disable this, set the environment variable * `NODE_LIBCURL_DISABLE_GLOBAL_INIT_CALL=false` */ Curl.globalInit = _Curl.globalInit; /** * Calls [`curl_global_cleanup()`](http://curl.haxx.se/libcurl/c/curl_global_cleanup.html) * * This is automatically called when the process is exiting. */ Curl.globalCleanup = _Curl.globalCleanup; /** * Returns libcurl version string. * * The string shows which libraries libcurl was built with and their versions, example: * ``` * libcurl/7.69.1-DEV OpenSSL/1.1.1d zlib/1.2.11 WinIDN libssh2/1.9.0_DEV nghttp2/1.40.0 * ``` */ Curl.getVersion = _Curl.getVersion; /** * Returns an object with a representation of the current libcurl version and their features/protocols. * * This is basically [`curl_version_info()`](https://curl.haxx.se/libcurl/c/curl_version_info.html) */ Curl.getVersionInfo = () => CurlVersionInfo; /** * Returns a string that looks like the one returned by * ```bash * curl -V * ``` * Example: * ``` * Version: libcurl/7.69.1-DEV OpenSSL/1.1.1d zlib/1.2.11 WinIDN libssh2/1.9.0_DEV nghttp2/1.40.0 * Protocols: dict, file, ftp, ftps, gopher, http, https, imap, imaps, ldap, ldaps, pop3, pop3s, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp * Features: AsynchDNS, IDN, IPv6, Largefile, SSPI, Kerberos, SPNEGO, NTLM, SSL, libz, HTTP2, HTTPS-proxy * ``` */ Curl.getVersionInfoString = () => { const version = Curl.getVersion(); const protocols = CurlVersionInfo.protocols.join(', '); const features = CurlVersionInfo.features.join(', '); return [ `Version: ${version}`, `Protocols: ${protocols}`, `Features: ${features}`, ].join('\n'); }; /** * Useful if you want to check if the current libcurl version is greater or equal than another one. * @param x major * @param y minor * @param z patch */ Curl.isVersionGreaterOrEqualThan = (x, y, z = 0) => { return _Curl.VERSION_NUM >= (x << 16) + (y << 8) + z; }; /** * This is the default user agent that is going to be used on all `Curl` instances. * * You can overwrite this in a per instance basis, calling `curlHandle.setOpt('USERAGENT', 'my-user-agent/1.0')`, or * by directly changing this property so it affects all newly created `Curl` instances. * * To disable this behavior set this property to `null`. */ Curl.defaultUserAgent = `node-libcurl/${pkg.version}`; /** * Returns the number of handles currently open in the internal {@link "Multi".Multi | `Multi`} handle being used. */ Curl.getCount = multiHandle.getCount; /** * Integer representing the current libcurl version. * * It was built the following way: * ``` * <8 bits major number> | <8 bits minor number> | <8 bits patch number>. * ``` * Version `7.69.1` is therefore returned as `0x074501` / `476417` */ Curl.VERSION_NUM = _Curl.VERSION_NUM; /** * This is a object with members resembling the `CURLINFO_*` libcurl constants. * * It can be used with {@link "Easy".Easy.getInfo | `Easy#getInfo`} or {@link getInfo | `Curl#getInfo`}. * * See the official documentation of [`curl_easy_getinfo()`](http://curl.haxx.se/libcurl/c/curl_easy_getinfo.html) * for reference. * * `CURLINFO_EFFECTIVE_URL` becomes `Curl.info.EFFECTIVE_URL` */ Curl.info = _Curl.info; /** * This is a object with members resembling the `CURLOPT_*` libcurl constants. * * It can be used with {@link "Easy".Easy.setOpt | `Easy#setOpt`} or {@link setOpt | `Curl#setOpt`}. * * See the official documentation of [`curl_easy_setopt()`](http://curl.haxx.se/libcurl/c/curl_easy_setopt.html) * for reference. * * `CURLOPT_URL` becomes `Curl.option.URL` */ Curl.option = _Curl.option; return Curl; })(); exports.Curl = Curl; //# sourceMappingURL=Curl.js.map