HEX
Server: Apache
System: Linux server1.myinter.net 4.18.0-553.147.1.el8_10.x86_64 #1 SMP Fri Jul 24 08:29:05 EDT 2026 x86_64
User: internet (1005)
PHP: 8.4.23
Disabled: passthru,system
Upload Files
File: /home/internet/public_html/whois/app.js
/**
 * WHOIS Terminal — app.js
 * Handles input cleaning, domain extraction, API call, and terminal rendering.
 */

(function () {
  'use strict';

  /* ── DOM refs ── */
  const input  = document.getElementById('domain-input');
  const btn    = document.getElementById('lookup-btn');
  const output = document.getElementById('output');

  /* ── Event bindings ── */
  input.addEventListener('keydown', function (e) {
    if (e.key === 'Enter') triggerLookup();
  });
  btn.addEventListener('click', triggerLookup);
  window.addEventListener('load', function () { input.focus(); });

  /* ──────────────────────────────────────────────
     INPUT CLEANING
     Accepts: URLs, comma/slash/pipe/semi/colon/space separated tokens,
              "Domain: example.com" style, punycode, etc.
  ────────────────────────────────────────────── */
  function cleanAndExtractDomain(raw) {
    if (!raw || !raw.trim()) return null;

    // 1. Trim outer whitespace
    var s = raw.trim();

    // 2. Split on common separators: , / | ; : and whitespace
    //    But we keep the token itself intact for URL parsing
    var tokens = s.split(/[\s,\/\|\;]+/);

    for (var i = 0; i < tokens.length; i++) {
      var token = tokens[i].trim();
      if (!token) continue;

      // Remove trailing colons (e.g. "Domain:" → "Domain", value is next token)
      token = token.replace(/:$/, '');
      if (!token) continue;

      // 3. If it looks like a URL (has protocol), extract hostname
      var hostname = '';
      if (/^https?:\/\//i.test(token)) {
        try {
          var url = new URL(token);
          hostname = url.hostname;
        } catch (e) {
          // malformed URL — strip protocol manually
          hostname = token.replace(/^https?:\/\//i, '').split('/')[0].split('?')[0].split('#')[0];
        }
      } else if (/^\/\//.test(token)) {
        // protocol-relative
        hostname = token.replace(/^\/\//, '').split('/')[0].split('?')[0];
      } else {
        hostname = token;
      }

      // 4. Remove any remaining path / query / fragment
      hostname = hostname.split('/')[0].split('?')[0].split('#')[0];

      // 5. Remove port if present (e.g. example.com:8080)
      hostname = hostname.replace(/:\d+$/, '');

      // 6. Remove leading "www." (case-insensitive)
      hostname = hostname.replace(/^www\./i, '');

      // 7. Lowercase
      hostname = hostname.toLowerCase();

      // 8. Validate: must have at least one dot, valid label chars (including xn-- for punycode)
      //    Label chars: a-z0-9, hyphens (not at start/end of label)
      if (isValidDomain(hostname)) {
        return hostname;
      }
    }

    return null; // no valid domain found
  }

  function isValidDomain(domain) {
    if (!domain) return false;
    if (domain.length > 253) return false;
    // Must contain at least one dot
    if (domain.indexOf('.') === -1) return false;
    // Split labels and validate each
    var labels = domain.split('.');
    if (labels.length < 2) return false;
    for (var i = 0; i < labels.length; i++) {
      var label = labels[i];
      if (!label) return false; // empty label (leading/trailing dot)
      if (label.length > 63) return false;
      // Valid: a-z, 0-9, hyphen; must not start or end with hyphen
      if (!/^[a-z0-9]([a-z0-9\-]*[a-z0-9])?$/.test(label) && !/^[a-z0-9]$/.test(label)) return false;
      // Allow xn-- punycode
    }
    // TLD must be at least 2 chars
    var tld = labels[labels.length - 1];
    if (tld.length < 2) return false;
    return true;
  }

  /* ──────────────────────────────────────────────
     LOOKUP TRIGGER
  ────────────────────────────────────────────── */
  function triggerLookup() {
    var raw = input.value;
    var domain = cleanAndExtractDomain(raw);

    if (!domain) {
      renderError('Invalid domain input', 'Could not extract a valid domain from the provided input.');
      return;
    }

    renderLoading(domain);
    fetchWhois(domain);
  }

  /* ──────────────────────────────────────────────
     API CALL
  ────────────────────────────────────────────── */
  function fetchWhois(domain) {
    var controller = null;
    var timeoutId  = null;
    var aborted    = false;

    if (typeof AbortController !== 'undefined') {
      controller = new AbortController();
      timeoutId  = setTimeout(function () {
        aborted = true;
        controller.abort();
      }, 12000); // 12s client-side timeout
    }

    var opts = { method: 'GET' };
    if (controller) opts.signal = controller.signal;

    var url = 'api/whois.php?domain=' + encodeURIComponent(domain);

    fetch(url, opts)
      .then(function (resp) {
        if (timeoutId) clearTimeout(timeoutId);
        if (!resp.ok) {
          throw new Error('HTTP ' + resp.status);
        }
        return resp.json();
      })
      .then(function (data) {
        if (data.error) {
          renderError('Lookup failed', data.error);
        } else {
          renderResult(data);
        }
      })
      .catch(function (err) {
        if (timeoutId) clearTimeout(timeoutId);
        if (aborted || (err && err.name === 'AbortError')) {
          renderError('Lookup failed', 'Request timed out — WHOIS server did not respond in time.');
        } else {
          renderError('Lookup failed', err.message || 'Network error or server unreachable.');
        }
      });
  }

  /* ──────────────────────────────────────────────
     RENDERING
  ────────────────────────────────────────────── */
  function clearOutput() {
    output.innerHTML = '';
  }

  function renderLoading(domain) {
    clearOutput();
    output.innerHTML =
      '<div class="cmd-echo">$ whois ' + esc(domain) + '</div>' +
      '<div class="loading-line">Querying WHOIS servers<span class="dots"></span></div>';
  }

  function renderResult(data) {
    var fields = [
      { label: 'Domain Name',   key: 'domainName'   },
      { label: 'Registrar URL', key: 'registrarUrl'  },
      { label: 'Updated Date',  key: 'updatedDate'   },
      { label: 'Creation Date', key: 'creationDate'  },
      { label: 'Expiry Date',   key: 'expiryDate'    },
      { label: 'Domain Status', key: 'domainStatus'  },
      { label: 'Name Server',   key: 'nameServer'    },
    ];

    var rows = '';
    fields.forEach(function (f) {
      var val = data[f.key];

      // Normalize arrays to comma-separated string
      if (Array.isArray(val)) {
        val = val.filter(Boolean).join(', ');
      }

      val = (val && String(val).trim()) ? String(val).trim() : 'N/A';
      var isNA = (val === 'N/A');

      rows +=
        '<div class="result-row">' +
          '<span class="result-label">' + esc(f.label) + ':</span>' +
          '<span class="result-value' + (isNA ? ' na' : '') + '">' + esc(val) + '</span>' +
        '</div>';
    });

    clearOutput();
    output.innerHTML =
      '<div class="cmd-echo">$ whois ' + esc(data.domainName || '') + '</div>' +
      '<hr class="divider" />' +
      '<div class="result-block">' + rows + '</div>';
  }

  function renderError(main, detail) {
    clearOutput();
    output.innerHTML =
      '<div class="error-block">' +
        '<div class="error-main">✖ ' + esc(main) + '</div>' +
        (detail ? '<div class="error-detail">' + esc(detail) + '</div>' : '') +
      '</div>';
  }

  /* ──────────────────────────────────────────────
     UTILITIES
  ────────────────────────────────────────────── */
  function esc(str) {
    return String(str)
      .replace(/&/g, '&amp;')
      .replace(/</g, '&lt;')
      .replace(/>/g, '&gt;')
      .replace(/"/g, '&quot;');
  }

})();