Repository: mozilla/pdf.js
File: web/autolinker.js (JavaScript)

Code before:
class Autolinker {
  static #index = 0;

  static #regex;

  static findLinks(text) {
    // Regex can be tested and verified at https://regex101.com/r/rXoLiT/2.
    this.#regex ??=
      /\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|\b[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[\p{P}<>]]+(?:\.[\S--[\p{P}<>]]+)+)/gmv;

    const [normalizedText, diffs] = normalize(text, { ignoreDashEOL: true });
    const matches = normalizedText.matchAll(this.#regex);
    const links = [];
    for (const match of matches) {
      const [url, emailDomain] = match;
      let raw;
      if (
        url.startsWith("www.") ||
        url.startsWith("http://") ||
        url.startsWith("https://")
      ) {
        raw = url;
      } else if (
        !emailDomain ||
        URL.parse(`http://${emailDomain}`)?.hostname.toLowerCase() ===
          emailDomain.toLowerCase()
      ) {
        raw = url.startsWith("mailto:") ? url : `mailto:${url}`;
      } else {
        continue;
      }
      const absoluteURL = createValidAbsoluteUrl(raw, null, {
        addDefaultProtocol: true,
      });
      if (absoluteURL) {
        const [index, length] = getOriginalIndex(
          diffs,
          match.index,
          url.length
        );
        links.push({ url: absoluteURL.href, index, length });
      }
    }
    return links;
  }

  static processLinks(pdfPageView) {
    return this.findLinks(
      pdfPageView._textHighlighter.textContentItemsStr.join("\n")
    ).map(link => createLinkAnnotation(link, pdfPageView, this.#index++));
  }

Review comment (suggestion):
Rather than checking `toLowerCase()` is equal across parsing, would it be better that we instead just check whether the parsed host name is IPv4 address? I mean something like
```js
/^\d+\.\d+\.\d+\.\d+$/.test(URL.parse(`http://${emailDomain}`)?.hostname)
```

According to [RFC 5321](https://www.rfc-editor.org/rfc/rfc5321.html), a IPv4 address can be used after `@` only in a bracketed address-literal form like `user@[8.8.8.8]`, and in address like `user@8.8.8.8`, the later part should be treated as domain name and go through DNS to resolve, which renders it practically unusable (there is no TLD consists of pure digits).

Also this new check in its current form would rule out IDN as domain. Is that intended and desirable? For example, `用户@例子.广告` is considered to be a valid [international email](https://en.wikipedia.org/wiki/International_email), but `URL.parse('http://例子.广告').hostname` would be `xn--fsqu00a.xn--4rr70v`. It's probably not accepted by the initial regex anyway, though.

Code after:
class Autolinker {
  static #index = 0;

  static #regex;

  static #numericTLDRegex;

  static findLinks(text) {
    // Regex can be tested and verified at https://regex101.com/r/rXoLiT/2.
    this.#regex ??=
      /\b(?:https?:\/\/|mailto:|www\.)(?:[\S--[\p{P}<>]]|\/|[\S--[\[\]]]+[\S--[\p{P}<>]])+|(?=\p{L})[\S--[@\p{Ps}\p{Pe}<>]]+@([\S--[\p{P}<>]]+(?:\.[\S--[\p{P}<>]]+)+)/gmv;

    const [normalizedText, diffs] = normalize(text, { ignoreDashEOL: true });
    const matches = normalizedText.matchAll(this.#regex);
    const links = [];
    for (const match of matches) {
      const [url, emailDomain] = match;
      let raw;
      if (
        url.startsWith("www.") ||
        url.startsWith("http://") ||
        url.startsWith("https://")
      ) {
        raw = url;
      } else if (emailDomain) {
        const hostname = URL.parse(`http://${emailDomain}`)?.hostname;
        if (!hostname) {
          continue;
        }
        this.#numericTLDRegex ??= /\.\d+$/;
        if (this.#numericTLDRegex.test(hostname)) {
          // Skip emails with a numeric TLD as domain.
          continue;
        }
      }
      raw ??= url.startsWith("mailto:") ? url : `mailto:${url}`;

      const absoluteURL = createValidAbsoluteUrl(raw, null, {
        addDefaultProtocol: true,
      });
      if (absoluteURL) {
        const [index, length] = getOriginalIndex(
          diffs,
          match.index,
          url.length
        );
        links.push({ url: absoluteURL.href, index, length });
      }
    }
    return links;
  }