From 08ca40733d836c087ce01dd9ccc30a9fac92ad9c Mon Sep 17 00:00:00 2001 From: Vasily Martynov Date: Tue, 18 Nov 2025 21:27:10 +1300 Subject: [PATCH] Replace depricated url.parse with the WHATWG URL API close #1380 --- src/http.ts | 15 +++++++++++---- src/server.ts | 36 ++++++++++++++++++++++++++++++------ src/wsdl/index.ts | 11 +++++++++-- test/client-test.js | 9 +++++++-- 4 files changed, 57 insertions(+), 14 deletions(-) diff --git a/src/http.ts b/src/http.ts index 961b620d9..89654e5f0 100644 --- a/src/http.ts +++ b/src/http.ts @@ -8,7 +8,6 @@ import { NtlmClient } from 'axios-ntlm'; import { randomUUID } from 'crypto'; import debugBuilder from 'debug'; import { ReadStream } from 'fs'; -import * as url from 'url'; import MIMEType from 'whatwg-mimetype'; import { gzipSync } from 'zlib'; import { IExOptions, IHeaders, IHttpClient, IOptions } from './types'; @@ -24,6 +23,13 @@ export interface IAttachment { body: NodeJS.ReadableStream; } +function getPortFromUrl(url: URL): string { + if (url.port) return url.port; + if (url.protocol.toLowerCase() === 'https:') return '443'; + if (url.protocol.toLowerCase() === 'http:') return '80'; + return ''; +} + /** * A class representing the http client * @param {Object} [options] Options object. It allows the customization of @@ -50,18 +56,19 @@ export class HttpClient implements IHttpClient { * @returns {Object} The http request object for the `request` module */ public buildRequest(rurl: string, data: any, exheaders?: IHeaders, exoptions: IExOptions = {}): any { - const curl = url.parse(rurl); + const curl = new URL(rurl); const method = data ? 'POST' : 'GET'; const host = curl.hostname; - const port = parseInt(curl.port, 10); + + const port = getPortFromUrl(curl); const headers: IHeaders = { 'User-Agent': 'node-soap/' + version, 'Accept': 'text/html,application/xhtml+xml,application/xml,text/xml;q=0.9,*/*;q=0.8', 'Accept-Encoding': 'none', 'Accept-Charset': 'utf-8', ...(exoptions.forever && { Connection: 'keep-alive' }), - 'Host': host + (isNaN(port) ? '' : ':' + port), + 'Host': host + (port ? ':' + port : ''), }; const mergeOptions = ['headers']; diff --git a/src/server.ts b/src/server.ts index 87e87fcc5..4dc30aa37 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,18 +5,18 @@ import { EventEmitter } from 'events'; import * as http from 'http'; -import * as url from 'url'; import { IOneWayOptions, IServerOptions, IServices, ISoapFault, ISoapServiceMethod } from './types'; import { WSDL } from './wsdl'; import { BindingElement, IPort } from './wsdl/elements'; import zlib from 'zlib'; -interface IExpressApp { +interface IExpressApp extends http.Server { route; use; } export type ServerType = http.Server | IExpressApp; + type Request = http.IncomingMessage & { body?: any }; type Response = http.ServerResponse; @@ -35,6 +35,28 @@ function getDateString(d) { return d.getUTCFullYear() + '-' + pad(d.getUTCMonth() + 1) + '-' + pad(d.getUTCDate()) + 'T' + pad(d.getUTCHours()) + ':' + pad(d.getUTCMinutes()) + ':' + pad(d.getUTCSeconds()) + 'Z'; } +function getServerBaseUrl(server: ServerType): string { + if (!server) { + return `http://localhost:8080`; + } + + if (typeof server.address !== 'function') { + return `http://${server.address}`; + } + + const address = server.address(); + + if (typeof address === 'string') { + return `http://${server.address}`; + } + + if (address.family.toLowerCase() === 'ipv6') { + return `http://localhost:${address.port}`; + } + + return `http://${address.address}:${address.port}`; +} + //eslint-disable-next-line @typescript-eslint/no-unsafe-declaration-merging export interface Server { emit(event: 'request', request: any, methodName: string): boolean; @@ -74,6 +96,7 @@ export class Server extends EventEmitter { private enableChunkedEncoding: boolean; private soapHeaders: any[]; private callback?: (err: any, res: any) => void; + private baseUrl: string; constructor(server: ServerType, path: string | RegExp, services: IServices, wsdl: WSDL, options?: IServerOptions) { super(); @@ -90,6 +113,7 @@ export class Server extends EventEmitter { this.onewayOptions = (options && options.oneWay) || {}; this.enableChunkedEncoding = options.enableChunkedEncoding === undefined ? true : !!options.enableChunkedEncoding; this.callback = options.callback ? options.callback : () => {}; + this.baseUrl = getServerBaseUrl(server); if (typeof path === 'string' && path[path.length - 1] !== '/') { path += '/'; } else if (path instanceof RegExp && path.source[path.source.length - 1] !== '/') { @@ -118,7 +142,7 @@ export class Server extends EventEmitter { return; } } - let reqPath = url.parse(req.url).pathname; + let reqPath = new URL(req.url, this.baseUrl).pathname; if (reqPath[reqPath.length - 1] !== '/') { reqPath += '/'; } @@ -225,7 +249,7 @@ export class Server extends EventEmitter { } private _requestListener(req: Request, res: Response) { - const reqParse = url.parse(req.url); + const reqParse = new URL(req.url, this.baseUrl); const reqQuery = reqParse.search; if (typeof this.log === 'function') { @@ -283,7 +307,7 @@ export class Server extends EventEmitter { } private _process(input, req: Request, res: Response, cb: (result: any, statusCode?: number) => any) { - const pathname = url.parse(req.url).pathname.replace(/\/$/, ''); + const pathname = new URL(req.url, this.baseUrl).pathname.replace(/\/$/, ''); const obj = this.wsdl.xmlToObject(input); const body = obj.Body; const headers = obj.Header; @@ -327,7 +351,7 @@ export class Server extends EventEmitter { for (name in ports) { portName = name; const port = ports[portName]; - const portPathname = url.parse(port.location).pathname.replace(/\/$/, ''); + const portPathname = new URL(port.location).pathname.replace(/\/$/, ''); if (typeof this.log === 'function') { this.log('info', 'Trying ' + portName + ' from path ' + portPathname, req); diff --git a/src/wsdl/index.ts b/src/wsdl/index.ts index 42b9d8e28..7e72c65b3 100644 --- a/src/wsdl/index.ts +++ b/src/wsdl/index.ts @@ -12,7 +12,6 @@ import * as _ from 'lodash'; import * as path from 'path'; import * as sax from 'sax'; import stripBom from 'strip-bom'; -import * as url from 'url'; import { HttpClient } from '../http'; import { NamespaceContext } from '../nscontext'; import { IOptions } from '../types'; @@ -1225,7 +1224,15 @@ export class WSDL { includePath = path.resolve(path.dirname(this.uri), include.location); } } else { - includePath = url.resolve(this.uri || '', include.location); + if (/^https?:/i.test(include.location)) { + includePath = include.location; + } else { + try { + includePath = new URL(this.uri || '', include.location).toString(); + } catch { + includePath = include.location; + } + } } const options = Object.assign({}, this.options); diff --git a/test/client-test.js b/test/client-test.js index 82872880c..fbe5e4b76 100644 --- a/test/client-test.js +++ b/test/client-test.js @@ -508,8 +508,13 @@ var fs = require('fs'), client.MyOperation( {}, function () { - assert.ok(client.lastRequestHeaders.Host.indexOf(':443') > -1); - done(); + try { + assert.ok(client.lastRequestHeaders.Host.indexOf(':443') > -1); + done(); + } catch (err) { + done(err); + throw err; + } }, null, { 'test-header': 'test' },