@stacksjs/whois 0.64.6 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/index.ts CHANGED
@@ -1,11 +1,12 @@
1
+ import type { SocksClientOptions } from 'socks'
2
+ import type { ProxyData, WhoIsOptions, WhoIsResponse } from './types'
1
3
  import Net from 'node:net'
4
+ import { log } from '@stacksjs/logging'
2
5
  import fetch from 'node-fetch'
3
- import type { SocksClientOptions } from 'socks'
4
6
  import { SocksClient } from 'socks'
5
- import PARAMETERS from './parameters'
6
- import SERVERS from './servers'
7
-
8
- const IANA_CHK_URL = 'https://www.iana.org/whois?q='
7
+ import { IANA_CHK_URL, PARAMETERS, SERVERS } from './constants'
8
+ import { ProxyType } from './types'
9
+ import { shallowCopy } from './utils'
9
10
 
10
11
  /**
11
12
  * Find the WhoIs server for the TLD from IANA WhoIs service. The TLD is be searched and the HTML response is parsed to extract the WhoIs server
@@ -13,43 +14,24 @@ const IANA_CHK_URL = 'https://www.iana.org/whois?q='
13
14
  * @param tld TLD of the domain
14
15
  * @returns WhoIs server which hosts the information for the domains of the TLD
15
16
  */
16
- async function findWhoIsServer(tld: string): Promise<string> {
17
+ export async function findWhoIsServer(tld: string): Promise<string> {
17
18
  const chkURL = IANA_CHK_URL + tld
18
19
 
19
20
  try {
20
21
  const res = await fetch(chkURL)
21
22
  if (res.ok) {
22
23
  const body = await res.text()
23
- const server = body.match(/whois:\s+(.*)\s+/)
24
- if (server?.[1]) return server[1]
24
+ const server = body.match(/whois:\s+(\S+)/)
25
+ if (server?.[1])
26
+ return server[1]
25
27
  }
26
- } catch (err) {
28
+ }
29
+ catch (err) {
27
30
  console.error('Error in getting WhoIs server data from IANA', err)
28
31
  }
29
32
 
30
33
  return ''
31
34
  }
32
- /**
33
- * Copy an Object with its values
34
- *
35
- * @param obj Object which needs to be copied
36
- * @returns A copy of the object
37
- */
38
- function shallowCopy<T>(obj: T): T {
39
- if (Array.isArray(obj)) {
40
- return obj.slice() as T // Clone the array
41
- }
42
-
43
- if (typeof obj === 'object' && obj !== null) {
44
- const copy: any = {}
45
- for (const key in obj) {
46
- if (Object.prototype.hasOwnProperty.call(obj, key)) copy[key] = shallowCopy(obj[key])
47
- }
48
- return copy as T
49
- }
50
-
51
- return obj // For primitive values, return as is
52
- }
53
35
 
54
36
  /**
55
37
  * Get whois server of the tld from servers list
@@ -57,7 +39,9 @@ function shallowCopy<T>(obj: T): T {
57
39
  * @param tld TLD of the domain
58
40
  * @returns WhoIs server which hosts the information for the domains of the TLD
59
41
  */
60
- function getWhoIsServer(tld: keyof typeof SERVERS): string | undefined {
42
+ export function getWhoIsServer(tld: keyof typeof SERVERS): string | undefined {
43
+ if (tld === 'com')
44
+ return 'whois.verisign-grs.com'
61
45
  return SERVERS[tld]
62
46
  }
63
47
 
@@ -69,134 +53,23 @@ function getWhoIsServer(tld: keyof typeof SERVERS): string | undefined {
69
53
  * @param domain Domain name
70
54
  * @returns TLD
71
55
  */
72
- function getTLD(domain: string): keyof typeof SERVERS {
73
- let tld: keyof typeof SERVERS | null = null
74
- let domainStr = domain
75
-
76
- while (true) {
77
- const domainData = domainStr.split('.')
78
- if (domainData.length < 2) break
79
-
80
- const tldCheck = domainData.slice(1).join('.') as keyof typeof SERVERS
81
- const server = SERVERS[tldCheck]
82
- if (server) {
83
- tld = tldCheck
84
- break
56
+ export function getTLD(domain: string): keyof typeof SERVERS {
57
+ const domainParts = domain.split('.')
58
+ for (let i = domainParts.length - 1; i > 0; i--) {
59
+ const possibleTLD = domainParts.slice(i).join('.') as keyof typeof SERVERS
60
+ if (SERVERS[possibleTLD]) {
61
+ return possibleTLD
85
62
  }
86
- domainStr = tldCheck
87
63
  }
88
-
89
- if (tld) return tld
90
-
91
- console.debug('TLD is not found in server list. Returning last element after split as TLD!')
92
-
93
- const domainData = domain.split('.')
94
- return domainData[domainData.length - 1] as keyof typeof SERVERS
64
+ // If no match found in SERVERS, return the last part
65
+ return domainParts[domainParts.length - 1] as keyof typeof SERVERS
95
66
  }
96
67
 
97
68
  // get whois query parameters if exist on parameters.json for whois server
98
- function getParameters(server: string): string | undefined {
69
+ export function getParameters(server: string): string | undefined {
99
70
  return (PARAMETERS as { [key: string]: string })[server]
100
71
  }
101
72
 
102
- /**
103
- * Type of the proxy. Either SOCKS4 or SOCKS5
104
- * @enum
105
- */
106
- export enum ProxyType {
107
- /**
108
- * SOCKS4 type of proxy
109
- */
110
- SOCKS4 = 0,
111
- /**
112
- * SOCKS5 type of proxy
113
- */
114
- SOCKS5 = 1,
115
- }
116
-
117
- /**
118
- * Proxy related data
119
- * @interface
120
- *
121
- */
122
- export interface ProxyData {
123
- /**
124
- * Proxy IP
125
- */
126
- ip: string
127
- /**
128
- * Proxy port
129
- */
130
- port: number
131
- /**
132
- * Username to connect to the proxy
133
- */
134
- username?: string | null
135
- /**
136
- * Password to connect to the proxy
137
- */
138
- password?: string | null
139
- /**
140
- * {@link ProxyType}
141
- */
142
- type: ProxyType
143
- }
144
-
145
- /**
146
- * WhoIs options
147
- * @interface
148
- */
149
- export interface WhoIsOptions {
150
- /**
151
- * TLD of the domain. If the {@link tld} is not provided (or null), then it will be automatically determined as to the given domain name
152
- */
153
- tld?: string | null
154
- /**
155
- * The encoding type used for WhoIs server and response. By default UTF-8 is used.
156
- */
157
- encoding?: string | null
158
-
159
- /** {@link ProxyData} */
160
- proxy?: ProxyData | null
161
- /**
162
- * The WhoIs server to collect data from. If not provided, the server will automatically determined using the {@link tld}
163
- */
164
- server?: string | null
165
- /**
166
- * The port of the WhoIs server. By default, port 43 is used.
167
- */
168
- serverPort?: number | null
169
- /**
170
- * Which data needs to be extracted/parsed from the WhoIs response.
171
- * An object can be passed which contains keys of the fields of the WhoIs response.
172
- * A copy of the provided object will be returned with the values filled for the provided keys.
173
- *
174
- * The keys can have default value of empty string. However, if the WhoIs response has multiple values for the same field (eg: 'Domain Status'),
175
- * then all the values can be collected by providing a default value of an Array([]).
176
- *
177
- * Following example shows an object used to collect 'Domain Name', 'Domain Status' (multiple values) and 'Registrar' from WhoIs response
178
- *
179
- * @example {'Domain Name': '', 'Domain Status': [], 'Registrar': ''}
180
- */
181
- parseData?: object | null
182
- }
183
-
184
- /**
185
- * Response returned from whois function. Contains the raw text from WhoIs server and parsed/fornatted WhoIs data (if parsed is true)
186
- *
187
- * @interface
188
- */
189
- export interface WhoIsResponse {
190
- /**
191
- * Raw text response from WhoIs server
192
- */
193
- _raw: string
194
- /**
195
- * Parsed/Formatted key-value pairs of the response (if parsed is true)
196
- */
197
- parsedData: any | null
198
- }
199
-
200
73
  /**
201
74
  * Parse collected raw WhoIs data
202
75
  *
@@ -220,22 +93,26 @@ export class WhoIsParser {
220
93
  if (letter === '\n' || (lastLetter === ':' && letter === ' ')) {
221
94
  if (lastStr.trim() in outputData) {
222
95
  lastField = lastStr.trim()
223
- } else if (lastField !== null) {
96
+ }
97
+ else if (lastField !== null) {
224
98
  const x = lastStr.trim()
225
99
  if (x !== '') {
226
100
  const obj = outputData[lastField]
227
- if (Array.isArray(obj)) obj.push(x)
101
+ if (Array.isArray(obj))
102
+ obj.push(x)
228
103
  else outputData[lastField] = x
229
104
 
230
105
  lastField = null
231
106
  }
232
107
  }
233
108
  lastStr = ''
234
- } else if (letter !== ':') {
109
+ }
110
+ else if (letter !== ':') {
235
111
  lastStr = lastStr + letter
236
112
  }
237
113
  lastLetter = letter as string
238
- if (lastStr === 'Record maintained by' || lastStr === '>>>') break
114
+ if (lastStr === 'Record maintained by' || lastStr === '>>>')
115
+ break
239
116
  }
240
117
 
241
118
  return outputData
@@ -256,7 +133,7 @@ export class WhoIsParser {
256
133
  'Updated Date': '',
257
134
  'Registry Expiry Date': '',
258
135
  'Domain Status': [],
259
- Registrar: '',
136
+ 'Registrar': '',
260
137
  }
261
138
  }
262
139
 
@@ -293,7 +170,8 @@ export async function tcpWhois(
293
170
  return new Promise((resolve, reject) => {
294
171
  try {
295
172
  socket.connect({ port, host: server }, () => {
296
- if (queryOptions !== '') socket.write(encoder.encode(`${queryOptions} ${domain}\r\n`))
173
+ if (queryOptions !== '')
174
+ socket.write(encoder.encode(`${queryOptions} ${domain}\r\n`))
297
175
  else socket.write(encoder.encode(`${domain}\r\n`))
298
176
  })
299
177
 
@@ -304,7 +182,8 @@ export async function tcpWhois(
304
182
  socket.on('error', (error) => {
305
183
  reject(error)
306
184
  })
307
- } catch (e) {
185
+ }
186
+ catch (e) {
308
187
  reject(e)
309
188
  }
310
189
  })
@@ -334,12 +213,15 @@ export async function tcpWhois(
334
213
  SocksClient.createConnection(options, (err, info) => {
335
214
  if (err) {
336
215
  reject(err)
337
- } else {
338
- if (!info) reject(new Error('No socket info received!'))
216
+ }
217
+ else {
218
+ if (!info)
219
+ reject(new Error('No socket info received!'))
339
220
 
340
221
  if (queryOptions !== '') {
341
222
  info?.socket.write(encoder.encode(`${queryOptions} ${domain}\r\n`))
342
- } else {
223
+ }
224
+ else {
343
225
  info?.socket.write(encoder.encode(`${domain}\r\n`))
344
226
  }
345
227
 
@@ -354,7 +236,7 @@ export async function tcpWhois(
354
236
  }
355
237
 
356
238
  /**
357
- * Collect WhoIs data for the mentioned {@link domain}. Parse the reveived response if {@link parse} is true, accordingly.
239
+ * Collect WhoIs data for the mentioned {@link domain}. Parse the received response if {@link parse} is true, accordingly.
358
240
  *
359
241
  * @param domain Domain name
360
242
  * @param parse Whether the raw text needs to be parsed/formatted or not
@@ -375,7 +257,8 @@ export async function whois(
375
257
  if (!options) {
376
258
  tld = getTLD(domain)
377
259
  proxy = null
378
- } else {
260
+ }
261
+ else {
379
262
  tld = options.tld ? options.tld : getTLD(domain)
380
263
  encoding = options.encoding ? options.encoding : 'utf-8'
381
264
  proxy = options.proxy ? options.proxy : null
@@ -386,16 +269,16 @@ export async function whois(
386
269
  if (server === '') {
387
270
  let serverData = getWhoIsServer(tld as keyof typeof SERVERS)
388
271
  if (!serverData) {
389
- console.debug(`No WhoIs server found for TLD: ${tld}! Attempting IANA WhoIs database for server!`)
272
+ log.debug(`No WhoIs server found for TLD: ${tld}! Attempting IANA WhoIs database for server!`)
390
273
  serverData = await findWhoIsServer(tld)
391
274
  if (!serverData) {
392
- console.debug('WhoIs server could not be found!')
275
+ log.debug('WhoIs server could not be found!')
393
276
  return {
394
277
  _raw: '',
395
278
  parsedData: null,
396
279
  }
397
280
  }
398
- console.debug(`WhoIs sever found for ${tld}: ${server}`)
281
+ log.debug(`WhoIs sever found for ${tld}: ${server}`)
399
282
  }
400
283
 
401
284
  server = serverData
@@ -405,7 +288,9 @@ export async function whois(
405
288
  const queryOptions = qOptions || ''
406
289
 
407
290
  try {
291
+ log.debug(`Attempting WHOIS lookup for ${domain} on server ${server}`)
408
292
  const rawData = await tcpWhois(domain, queryOptions, server, port, encoding, proxy)
293
+ log.debug(`Raw WHOIS data received:`, rawData)
409
294
  if (!parse) {
410
295
  const parsedData = WhoIsParser.parseData(rawData, null)
411
296
  return {
@@ -415,7 +300,8 @@ export async function whois(
415
300
  }
416
301
 
417
302
  let outputData: any | null = null
418
- if (options?.parseData) outputData = shallowCopy(options.parseData)
303
+ if (options?.parseData)
304
+ outputData = shallowCopy(options.parseData)
419
305
 
420
306
  try {
421
307
  const parsedData = WhoIsParser.parseData(rawData, outputData)
@@ -423,14 +309,17 @@ export async function whois(
423
309
  _raw: rawData,
424
310
  parsedData,
425
311
  }
426
- } catch (err) {
312
+ }
313
+ catch (err) {
427
314
  console.error('Error in parsing WhoIs data!', err)
428
315
  return {
429
316
  _raw: rawData,
430
317
  parsedData: null,
431
318
  }
432
319
  }
433
- } catch (err) {
320
+ }
321
+ catch (err: any) {
322
+ log.debug(`Error in WHOIS lookup for ${domain} on server ${server}`, err)
434
323
  return {
435
324
  _raw: '',
436
325
  parsedData: null,
@@ -439,6 +328,7 @@ export async function whois(
439
328
  }
440
329
 
441
330
  export function lookup(domain: string, options: WhoIsOptions | null = null): Promise<WhoIsResponse> {
331
+ log.debug(`Lookup called for ${domain}`)
442
332
  return whois(domain, true, options)
443
333
  }
444
334
 
@@ -464,7 +354,8 @@ export async function batchWhois(
464
354
  let response: WhoIsResponse[] = []
465
355
 
466
356
  if (parallel) {
467
- if (threads > domains.length) threads = domains.length
357
+ if (threads > domains.length)
358
+ threads = domains.length
468
359
 
469
360
  for (let i = 0; i < domains.length; i += threads) {
470
361
  const batch = domains.slice(i, i + threads)
@@ -474,7 +365,8 @@ export async function batchWhois(
474
365
  }),
475
366
  )
476
367
  }
477
- } else {
368
+ }
369
+ else {
478
370
  for (let i = 0; i < domains.length; i++) {
479
371
  const res = await whois(domains[i] as string, parse, options)
480
372
  response.push(res)
@@ -483,3 +375,9 @@ export async function batchWhois(
483
375
 
484
376
  return response
485
377
  }
378
+
379
+ export * from './constants'
380
+ export * from './types'
381
+
382
+ export { SocksClient } from 'socks'
383
+ export type { SocksClientOptions } from 'socks'
package/src/types.ts ADDED
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Response returned from whois function. Contains the raw text from WhoIs server and parsed/fornatted WhoIs data (if parsed is true)
3
+ *
4
+ * @interface
5
+ */
6
+ export interface WhoIsResponse {
7
+ /**
8
+ * Raw text response from WhoIs server
9
+ */
10
+ _raw: string
11
+ /**
12
+ * Parsed/Formatted key-value pairs of the response (if parsed is true)
13
+ */
14
+ parsedData: any | null
15
+ }
16
+
17
+ /**
18
+ * Type of the proxy. Either SOCKS4 or SOCKS5
19
+ * @enum
20
+ */
21
+ export enum ProxyType {
22
+ /**
23
+ * SOCKS4 type of proxy
24
+ */
25
+ SOCKS4 = 0,
26
+ /**
27
+ * SOCKS5 type of proxy
28
+ */
29
+ SOCKS5 = 1,
30
+ }
31
+
32
+ /**
33
+ * Proxy related data
34
+ * @interface
35
+ *
36
+ */
37
+ export interface ProxyData {
38
+ /**
39
+ * Proxy IP
40
+ */
41
+ ip: string
42
+ /**
43
+ * Proxy port
44
+ */
45
+ port: number
46
+ /**
47
+ * Username to connect to the proxy
48
+ */
49
+ username?: string | null
50
+ /**
51
+ * Password to connect to the proxy
52
+ */
53
+ password?: string | null
54
+ /**
55
+ * {@link ProxyType}
56
+ */
57
+ type: ProxyType
58
+ }
59
+
60
+ /**
61
+ * WhoIs options
62
+ * @interface
63
+ */
64
+ export interface WhoIsOptions {
65
+ /**
66
+ * TLD of the domain. If the {@link tld} is not provided (or null), then it will be automatically determined as to the given domain name
67
+ */
68
+ tld?: string | null
69
+ /**
70
+ * The encoding type used for WhoIs server and response. By default UTF-8 is used.
71
+ */
72
+ encoding?: string | null
73
+
74
+ /** {@link ProxyData} */
75
+ proxy?: ProxyData | null
76
+ /**
77
+ * The WhoIs server to collect data from. If not provided, the server will automatically determined using the {@link tld}
78
+ */
79
+ server?: string | null
80
+ /**
81
+ * The port of the WhoIs server. By default, port 43 is used.
82
+ */
83
+ serverPort?: number | null
84
+ /**
85
+ * Which data needs to be extracted/parsed from the WhoIs response.
86
+ * An object can be passed which contains keys of the fields of the WhoIs response.
87
+ * A copy of the provided object will be returned with the values filled for the provided keys.
88
+ *
89
+ * The keys can have default value of empty string. However, if the WhoIs response has multiple values for the same field (eg: 'Domain Status'),
90
+ * then all the values can be collected by providing a default value of an Array([]).
91
+ *
92
+ * Following example shows an object used to collect 'Domain Name', 'Domain Status' (multiple values) and 'Registrar' from WhoIs response
93
+ *
94
+ * @example {'Domain Name': '', 'Domain Status': [], 'Registrar': ''}
95
+ */
96
+ parseData?: object | null
97
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Copy an Object with its values
3
+ *
4
+ * @param obj Object which needs to be copied
5
+ * @returns A copy of the object
6
+ */
7
+ export function shallowCopy<T>(obj: T): T {
8
+ if (Array.isArray(obj)) {
9
+ return obj.slice() as T // Clone the array
10
+ }
11
+
12
+ if (typeof obj === 'object' && obj !== null) {
13
+ const copy: any = {}
14
+ for (const key in obj) {
15
+ if (Object.prototype.hasOwnProperty.call(obj, key))
16
+ copy[key] = shallowCopy(obj[key])
17
+ }
18
+ return copy as T
19
+ }
20
+
21
+ return obj // For primitive values, return as is
22
+ }
package/src/parameters.ts DELETED
@@ -1,4 +0,0 @@
1
- export default {
2
- 'whois.denic.de': '-T dn,ace',
3
- 'whois.nic.fr': '-V Md5.2',
4
- }