Issues (103)

app/js/common/whoiswrapper.js (1 issue)

Labels
Severity
1
// jshint esversion: 8, -W069
2
/** global: conversion, general, assumptions, timeout, follow, timeBetween */
3
4
const psl = require('psl'),
5
  puny = require('punycode'),
6
  uts46 = require('idna-uts46'),
7
  whois = require('whois'),
8
  parseRawData = require('./parseRawData'),
9
  debug = require('debug')('common.whoisWrapper'),
10
  {
11
    getDate
12
  } = require('./conversions'),
13
  settings = require('./settings').load();
14
15
16
/*
17
  lookupPromise
18
    Promisified whois lookup
19
 */
20
const lookupPromise = (...args) => {
21
  return new Promise((resolve, reject) => {
22
    whois.lookup(...args, (err, data) => {
23
      if (err) return reject(err);
24
      resolve(data);
25
      return undefined;
26
    });
27
  });
28
};
29
30
/*
31
  lookup
32
    Do a domain whois lookup
33
  parameters
34
    domain (string) - Domain name
35
    options (object) - Lookup options object, refer to 'defaultoptions' var or 'settings.lookup.general/server'
36
 */
37
async function lookup(domain, options = getWhoisOptions()) {
38
  var {
39
    'lookup.conversion': conversion,
40
    'lookup.general': general
41
  } = settings,
42
  domainResults;
43
44
  try {
45
    domain = conversion.enabled ? convertDomain(domain) : domain;
46
    domain = general.psl ? psl.get(domain).replace(/((\*\.)*)/g, '') : domain;
47
48
    debug("Looking up for {0}".format(domain));
49
    domainResults = await lookupPromise(domain, options);
0 ignored issues
show
The call to lookupPromise seems to have too many arguments starting with options.
Loading history...
50
  } catch (e) {
51
    domainResults = "Whois lookup error, {0}".format(e);
52
  }
53
54
  return domainResults;
55
}
56
57
/*
58
  toJSON
59
    Transform a given string to JSON object
60
  parameters
61
    resultsText (string) - whois domain reply string
62
 */
63
function toJSON(resultsText) {
64
  if (typeof resultsText === 'string' && resultsText.includes("lookup: timeout")) return "timeout";
65
66
  if (typeof resultsText === 'object') {
67
    //JSON.stringify(resultsText, null, 2);
68
    resultsText.map(function(data) {
69
      data.data = parseRawData(data.data);
70
      return data;
71
    });
72
  } else {
73
    return parseRawData(preStringStrip(resultsText));
74
  }
75
76
  return undefined;
77
}
78
79
/*
80
  isDomainAvailable
81
    Check domain whois reply for its avalability
82
  parameters
83
    resultsText (string) - Pure text whois reply
84
    resultsJSON (JSON Object) - JSON transformed whois reply
85
 */
86
function isDomainAvailable(resultsText, resultsJSON) {
87
  const {
88
    'lookup.assumptions': assumptions
89
  } = settings;
90
91
  resultsJSON = resultsJSON || 0;
92
93
  if (resultsJSON === 0) resultsJSON = toJSON(resultsText);
94
95
  var domainParams = getDomainParameters(null, null, null, resultsJSON, true);
96
  var controlDate = getDate(Date.now());
97
98
  switch (true) {
99
    /*
100
      Special cases
101
     */
102
    case (resultsText.includes('Uniregistry') && resultsText.includes('Query limit exceeded')):
103
      return (assumptions.uniregistry ? 'unavailable' : 'error:ratelimiting');
104
105
      /*
106
        Available checks
107
       */
108
109
      // Not found cases & variants
110
      //case (resultsText.includes('ERROR:101: no entries found')):
111
112
113
      // No match cases & variants
114
    case (resultsText.includes('No match for domain')):
115
    case (resultsText.includes('- No Match')):
116
    case (resultsText.includes('NO MATCH:')):
117
    case (resultsText.includes('No match for')):
118
    case (resultsText.includes('No match')):
119
    case (resultsText.includes('No matching record.')):
120
    case (resultsText.includes('Nincs talalat')):
121
122
      // Status cases & variants
123
    case (resultsText.includes('Status: AVAILABLE')):
124
    case (resultsText.includes('Status:             AVAILABLE')):
125
    case (resultsText.includes('Status: 	available')):
126
    case (resultsText.includes('Status: free')):
127
    case (resultsText.includes('Status: Not Registered')):
128
    case (resultsText.includes('query_status: 220 Available')):
129
130
      // Unique cases
131
    case (domainParams.expiryDate - controlDate < 0):
132
    case (resultsText.includes('This domain name has not been registered')):
133
    case (resultsText.includes('The domain has not been registered')):
134
    case (resultsText.includes('This query returned 0 objects')):
135
    case (resultsText.includes(' is free') && domainParams.whoisreply.length < 50):
136
    case (resultsText.includes('domain name not known in')):
137
    case (resultsText.includes('registration status: available')):
138
    case (resultsText.includes('whois.nic.bo') && domainParams.whoisreply.length < 55):
139
    case (resultsText.includes('Object does not exist')):
140
    case (resultsText.includes('The queried object does not exist')):
141
    case (resultsText.includes('Not Registered -')):
142
    case (resultsText.includes('is available for registration')):
143
    case (resultsText.includes('is available for purchase')):
144
    case (resultsText.includes('DOMAIN IS NOT A REGISTERD')):
145
    case (resultsText.includes('No such domain')):
146
    case (resultsText.includes('No_Se_Encontro_El_Objeto')):
147
    case (resultsText.includes('Domain unknown')):
148
    case (resultsText.includes('No information available about domain name')):
149
    case (resultsText.includes('Error.') && resultsText.includes('SaudiNIC')):
150
    case (resultsText.includes('is not valid!')): // ???
151
      return 'available';
152
153
      /*
154
        Unavailable checks
155
       */
156
    case (resultsJSON.hasOwnProperty('domainName')): // Has domain name
157
    case (resultsText.includes('Domain Status:ok')): // Domain name is ok
158
    case (resultsText.includes('Expiration Date:')): // Has expiration date (1)
159
    case (resultsText.includes('Expiry Date:')): // Has Expiration date (2)
160
    case (resultsText.includes('Status: connect')): // Has connect status
161
    case (resultsText.includes('Changed:')): // Has a changed date
162
    case (Object.keys(resultsJSON).length > 5): // JSON has more than 5 keys (probably taken?)
163
    case (resultsText.includes('organisation: Internet Assigned Numbers Authority')): // Is controlled by IANA
164
      return 'unavailable';
165
166
      /*
167
        Error checks
168
       */
169
170
      // Error, null or no contents
171
    case (resultsText === null):
172
    case (resultsText === ''):
173
      return 'error:nocontent';
174
175
      // Error, unauthorized
176
    case (resultsText.includes('You  are  not  authorized  to  access or query our Whois')):
177
      return 'error:unauthorized';
178
179
      // Error, rate limiting
180
    case (resultsText.includes('IP Address Has Reached Rate Limit')):
181
    case (resultsText.includes('Too many connection attempts')):
182
    case (resultsText.includes('Your request is being rate limited')):
183
    case (resultsText.includes('Your query is too often.')):
184
    case (resultsText.includes('Your connection limit exceeded.')):
185
      return (assumptions.ratelimit ? 'unavailable' : 'error:ratelimiting');
186
187
      // Error, unretrivable
188
    case (resultsText.includes('Could not retrieve Whois data')):
189
      return 'error:unretrivable';
190
191
      // Error, forbidden
192
    case (resultsText.includes('si is forbidden')): // .si is forbidden
193
    case (resultsText.includes('Requests of this client are not permitted')): // .ch forbidden
194
      return 'error:forbidden';
195
196
      // Error, reserved by regulator
197
    case (resultsText.includes('reserved by aeDA Regulator')): // Reserved for aeDA regulator
198
      return 'error:reservedbyregulator';
199
200
      // Error, unregistrable.
201
    case (resultsText.includes('third-level domains may not start with')):
202
      return 'error:unregistrable';
203
204
      // Error, reply error
205
    case (resultsJSON.hasOwnProperty('error')):
206
    case (resultsJSON.hasOwnProperty('errno')):
207
    case (resultsText.includes('error ')):
208
    case (resultsText.includes('error')): // includes plain error, may cause false negatives? i.e. error.com lookup
209
    case (resultsText.includes('Error')): // includes plain error, may cause false negatives? i.e. error.com lookup
210
    case (resultsText.includes('ERROR:101:')):
211
    case (resultsText.includes('Whois lookup error')):
212
    case (resultsText.includes('can temporarily not be answered')):
213
    case (resultsText.includes('Invalid input')):
214
      return 'error:replyerror';
215
216
      /*
217
         Error throw
218
           If every check fails throw Error, unparsable
219
        */
220
221
    default:
222
      return (assumptions.unparsable ? 'available' : 'error:unparsable');
223
  }
224
}
225
226
/*
227
  getDomainParameters
228
    Get streamlined domain results object
229
  parameters
230
    domain (string) - Domain name
231
    status (string) - isDomainAvailable result, is domain Available
232
    resultsText (string) - Pure text whois reply
233
    resultsJSON (JSON Object) - JSON transformed whois reply
234
    isAuxiliary (boolean) - Is auxiliary function to domain availability check, if used in "isDomainAvailable" fn
235
 */
236
function getDomainParameters(domain, status, resultsText, resultsJSON, isAuxiliary = false) {
237
  var results = {};
238
239
  results.domain = domain;
240
  results.status = status;
241
  results.registrar = resultsJSON.registrar;
242
  results.company =
243
    resultsJSON.registrantOrganization ||
244
    resultsJSON.registrant ||
245
    resultsJSON.registrantOrganization ||
246
    resultsJSON.adminName ||
247
    resultsJSON.ownerName ||
248
    resultsJSON.contact ||
249
    resultsJSON.name;
250
  results.creationDate = getDate(
251
    resultsJSON.creationDate ||
252
    resultsJSON.createdDate ||
253
    resultsJSON.created ||
254
    resultsJSON.creationDate ||
255
    resultsJSON.registered ||
256
    resultsJSON.registeredOn);
257
  results.updateDate = getDate(
258
    resultsJSON.updatedDate ||
259
    resultsJSON.lastUpdated ||
260
    resultsJSON.UpdatedDate ||
261
    resultsJSON.changed ||
262
    resultsJSON.lastModified ||
263
    resultsJSON.lastUpdate);
264
  results.expiryDate = getDate(
265
    resultsJSON.expires ||
266
    resultsJSON.registryExpiryDate ||
267
    resultsJSON.expiryDate ||
268
    resultsJSON.registrarRegistrationExpirationDate ||
269
    resultsJSON.expire ||
270
    resultsJSON.expirationDate ||
271
    resultsJSON.expiresOn ||
272
    resultsJSON.paidTill);
273
  results.whoisReply = resultsText;
274
  results.whoisJson = resultsJSON;
275
276
  //debug(results);
277
278
  return results;
279
}
280
281
/*
282
  convertDomain
283
    Convert a given domain using a defined algorithm in appSettings
284
  parameters
285
    domain (string) - Domain to be converted
286
  modes
287
    punycode - Punycode
288
    uts46 - IDNA2008
289
    uts46-transitional - IDNA2003
290
    ascii - Filter out non-ASCII characters
291
    anything else - No conversion
292
 */
293
function convertDomain(domain, mode) {
294
  var {
295
    'lookup.conversion': conversion
296
  } = settings;
297
298
  mode = mode || conversion.algorithm;
299
300
  switch (mode) {
301
    case 'punycode':
302
      return puny.encode(domain);
303
    case 'uts46':
304
      return uts46.toAscii(domain);
305
    case 'uts46-transitional':
306
      return uts46.toAscii(domain, {
307
        transitional: true
308
      });
309
    case 'ascii':
310
      return domain.replace(/[^\x00-\x7F]/g, "");
311
312
    default:
313
      return domain;
314
  }
315
}
316
317
/*
318
  getWhoisOptions
319
    Create whois options based on appSettings
320
 */
321
function getWhoisOptions() {
322
  const {
323
    'lookup.general': general
324
  } = settings;
325
326
  var options = {},
327
    follow = 'follow',
328
    timeout = 'timeout';
329
330
  options.server = general.server;
331
  options.follow = getWhoisParameters(follow);
332
  options.timeout = getWhoisParameters(timeout);
333
  options.verbose = general.verbose;
334
335
  return options;
336
}
337
338
/*
339
  getWhoisParameters
340
    Get request follow level/depth
341
  parameters
342
    parameter (string) - Whois options parameter
343
      'follow' - Follow depth
344
      'timeout' - Timeout
345
      'timebetween' - Time between requests
346
 */
347
function getWhoisParameters(parameter) {
348
  const {
349
    'lookup.randomize.follow': follow,
350
    'lookup.randomize.timeout': timeout,
351
    'lookup.randomize.timeBetween': timeBetween,
352
    'lookup.general': general
353
  } = settings;
354
355
  switch (parameter) {
356
    case 'follow':
357
      debug("Follow depth, 'random': {0}, 'maximum': {1}, 'minimum': {2}, 'default': {3}".format(follow.randomize, follow.maximumDepth, follow.minimumDepth, general.follow));
358
      return (follow.randomize ? getRandomInt(follow.minimumDepth, follow.maximumDepth) : general.follow);
359
360
    case 'timeout':
361
      debug("Timeout, 'random': {0}, 'maximum': {1}, 'minimum': {2}, 'default': {3}".format(timeout.randomize, timeout.maximum, timeout.minimum, general.timeout));
362
      return (timeout.randomize ? getRandomInt(timeout.minimum, timeout.maximum) : general.timeout);
363
364
    case 'timebetween':
365
      debug("Timebetween, 'random': {0}, 'maximum': {1}, 'minimum': {2}, 'default': {3}".format(timeBetween.randomize, timeBetween.maximum, timeBetween.minimum, general.timeBetween));
366
      return (timeBetween.randomize ? getRandomInt(timeBetween.minimum, timeBetween.maximum) : general.timeBetween);
367
368
    default:
369
      return undefined;
370
371
  }
372
373
}
374
375
/*
376
  getRandomInt
377
    Get a random integer between two values
378
  parameters
379
    min (integer) - Minimum value
380
    max (integer) - Maximum value
381
 */
382
function getRandomInt(min, max) {
383
  return Math.floor((Math.random() * parseInt(max)) + parseInt(min));
384
}
385
386
/*
387
  preStringStrip
388
    Pre strip a given string, space key value pairs
389
  parameters
390
    str (string) - String to be stripped
391
 */
392
function preStringStrip(str) {
393
  return str.toString().replace(/\:\t{1,2}/g, ": "); // Space key value pairs
394
}
395
396
module.exports = {
397
  lookup: lookup,
398
  toJSON: toJSON,
399
  isDomainAvailable: isDomainAvailable,
400
  preStringStrip: preStringStrip,
401
  getDomainParameters: getDomainParameters,
402
  convertDomain: convertDomain
403
};
404