From 717534ee353682f3bcf33e60a8af4292626d4441 Mon Sep 17 00:00:00 2001
From: Luke Karrys <luke@lukekarrys.com>
Date: Thu, 15 Jun 2023 12:21:14 -0700
Subject: [PATCH] fix: better handling of whitespace (#564)

CVE: CVE-2022-25883

Upstream-Status: Backport [https://github.com/npm/node-semver/commit/717534ee353682f3bcf33e60a8af4292626d4441]

Signed-off-by: Archana Polampalli <archana.polampalli@windriver.com>
---
 .../node_modules/semver/classes/comparator.js |  3 +-
 deps/npm/node_modules/semver/classes/range.js | 64 +++++++++++--------
 .../npm/node_modules/semver/classes/semver.js |  2 +-
 .../node_modules/semver/functions/coerce.js   |  2 +-
 deps/npm/node_modules/semver/internal/re.js   | 11 ++++
 deps/npm/node_modules/semver/package.json     |  2 +-
 6 files changed, 53 insertions(+), 31 deletions(-)

diff --git a/deps/npm/node_modules/semver/classes/comparator.js b/deps/npm/node_modules/semver/classes/comparator.js
index 62cd204..c909446 100644
--- a/deps/npm/node_modules/semver/classes/comparator.js
+++ b/deps/npm/node_modules/semver/classes/comparator.js
@@ -16,6 +16,7 @@ class Comparator {
       }
     }

+    comp = comp.trim().split(/\s+/).join(' ')
     debug('comparator', comp, options)
     this.options = options
     this.loose = !!options.loose
@@ -129,7 +130,7 @@ class Comparator {
 module.exports = Comparator

 const parseOptions = require('../internal/parse-options')
-const { re, t } = require('../internal/re')
+const { safeRe: re, t } = require('../internal/re')
 const cmp = require('../functions/cmp')
 const debug = require('../internal/debug')
 const SemVer = require('./semver')
diff --git a/deps/npm/node_modules/semver/classes/range.js b/deps/npm/node_modules/semver/classes/range.js
index 7dc24bc..8e2e1f9 100644
--- a/deps/npm/node_modules/semver/classes/range.js
+++ b/deps/npm/node_modules/semver/classes/range.js
@@ -26,19 +26,26 @@ class Range {
     this.loose = !!options.loose
     this.includePrerelease = !!options.includePrerelease

-    // First, split based on boolean or ||
+    // First reduce all whitespace as much as possible so we do not have to rely
+    // on potentially slow regexes like \s*. This is then stored and used for
+    // future error messages as well.
     this.raw = range
-    this.set = range
+      .trim()
+      .split(/\s+/)
+      .join(' ')
+
+    // First, split on ||
+    this.set = this.raw
       .split('||')
       // map the range to a 2d array of comparators
-      .map(r => this.parseRange(r.trim()))
+      .map(r => this.parseRange(r))
       // throw out any comparator lists that are empty
       // this generally means that it was not a valid range, which is allowed
       // in loose mode, but will still throw if the WHOLE range is invalid.
       .filter(c => c.length)

     if (!this.set.length) {
-      throw new TypeError(`Invalid SemVer Range: ${range}`)
+      throw new TypeError(`Invalid SemVer Range: ${this.raw}`)
     }

     // if we have any that are not the null set, throw out null sets.
@@ -64,9 +71,7 @@ class Range {

   format () {
     this.range = this.set
-      .map((comps) => {
-        return comps.join(' ').trim()
-      })
+      .map((comps) => comps.join(' ').trim())
       .join('||')
       .trim()
     return this.range
@@ -77,8 +82,6 @@ class Range {
   }

   parseRange (range) {
-    range = range.trim()
-
     // memoize range parsing for performance.
     // this is a very hot path, and fully deterministic.
     const memoOpts = Object.keys(this.options).join(',')
@@ -103,9 +106,6 @@ class Range {
     // `^ 1.2.3` => `^1.2.3`
     range = range.replace(re[t.CARETTRIM], caretTrimReplace)

-    // normalize spaces
-    range = range.split(/\s+/).join(' ')
-
     // At this point, the range is completely trimmed and
     // ready to be split into comparators.

@@ -200,7 +200,7 @@ const Comparator = require('./comparator')
 const debug = require('../internal/debug')
 const SemVer = require('./semver')
 const {
-  re,
+  safeRe: re,
   t,
   comparatorTrimReplace,
   tildeTrimReplace,
@@ -252,10 +252,13 @@ const isX = id => !id || id.toLowerCase() === 'x' || id === '*'
 // ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0-0
 // ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0-0
 // ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0-0
-const replaceTildes = (comp, options) =>
-  comp.trim().split(/\s+/).map((c) => {
-    return replaceTilde(c, options)
-  }).join(' ')
+const replaceTildes = (comp, options) => {
+  return comp
+    .trim()
+    .split(/\s+/)
+    .map((c) => replaceTilde(c, options))
+    .join(' ')
+}

 const replaceTilde = (comp, options) => {
   const r = options.loose ? re[t.TILDELOOSE] : re[t.TILDE]
@@ -291,10 +294,13 @@ const replaceTilde = (comp, options) => {
 // ^1.2, ^1.2.x --> >=1.2.0 <2.0.0-0
 // ^1.2.3 --> >=1.2.3 <2.0.0-0
 // ^1.2.0 --> >=1.2.0 <2.0.0-0
-const replaceCarets = (comp, options) =>
-  comp.trim().split(/\s+/).map((c) => {
-    return replaceCaret(c, options)
-  }).join(' ')
+const replaceCarets = (comp, options) => {
+  return comp
+    .trim()
+    .split(/\s+/)
+    .map((c) => replaceCaret(c, options))
+    .join(' ')
+}

 const replaceCaret = (comp, options) => {
   debug('caret', comp, options)
@@ -351,9 +357,10 @@ const replaceCaret = (comp, options) => {

 const replaceXRanges = (comp, options) => {
   debug('replaceXRanges', comp, options)
-  return comp.split(/\s+/).map((c) => {
-    return replaceXRange(c, options)
-  }).join(' ')
+  return comp
+    .split(/\s+/)
+    .map((c) => replaceXRange(c, options))
+    .join(' ')
 }

 const replaceXRange = (comp, options) => {
@@ -436,12 +443,15 @@ const replaceXRange = (comp, options) => {
 const replaceStars = (comp, options) => {
   debug('replaceStars', comp, options)
   // Looseness is ignored here.  star is always as loose as it gets!
-  return comp.trim().replace(re[t.STAR], '')
+  return comp
+    .trim()
+    .replace(re[t.STAR], '')
 }

 const replaceGTE0 = (comp, options) => {
   debug('replaceGTE0', comp, options)
-  return comp.trim()
+  return comp
+    .trim()
     .replace(re[options.includePrerelease ? t.GTE0PRE : t.GTE0], '')
 }

@@ -479,7 +489,7 @@ const hyphenReplace = incPr => ($0,
     to = `<=${to}`
   }

-  return (`${from} ${to}`).trim()
+  return `${from} ${to}`.trim()
 }

 const testSet = (set, version, options) => {
diff --git a/deps/npm/node_modules/semver/classes/semver.js b/deps/npm/node_modules/semver/classes/semver.js
index af62955..ad4e877 100644
--- a/deps/npm/node_modules/semver/classes/semver.js
+++ b/deps/npm/node_modules/semver/classes/semver.js
@@ -1,6 +1,6 @@
 const debug = require('../internal/debug')
 const { MAX_LENGTH, MAX_SAFE_INTEGER } = require('../internal/constants')
-const { re, t } = require('../internal/re')
+const { safeRe: re, t } = require('../internal/re')

 const parseOptions = require('../internal/parse-options')
 const { compareIdentifiers } = require('../internal/identifiers')
diff --git a/deps/npm/node_modules/semver/functions/coerce.js b/deps/npm/node_modules/semver/functions/coerce.js
index 2e01452..febbff9 100644
--- a/deps/npm/node_modules/semver/functions/coerce.js
+++ b/deps/npm/node_modules/semver/functions/coerce.js
@@ -1,6 +1,6 @@
 const SemVer = require('../classes/semver')
 const parse = require('./parse')
-const { re, t } = require('../internal/re')
+const { safeRe: re, t } = require('../internal/re')

 const coerce = (version, options) => {
   if (version instanceof SemVer) {
diff --git a/deps/npm/node_modules/semver/internal/re.js b/deps/npm/node_modules/semver/internal/re.js
index ed88398..f73ef1a 100644
--- a/deps/npm/node_modules/semver/internal/re.js
+++ b/deps/npm/node_modules/semver/internal/re.js
@@ -4,16 +4,27 @@ exports = module.exports = {}

 // The actual regexps go on exports.re
 const re = exports.re = []
+const safeRe = exports.safeRe = []
 const src = exports.src = []
 const t = exports.t = {}
 let R = 0

 const createToken = (name, value, isGlobal) => {
+  // Replace all greedy whitespace to prevent regex dos issues. These regex are
+  // used internally via the safeRe object since all inputs in this library get
+  // normalized first to trim and collapse all extra whitespace. The original
+  // regexes are exported for userland consumption and lower level usage. A
+  // future breaking change could export the safer regex only with a note that
+  // all input should have extra whitespace removed.
+  const safe = value
+    .split('\\s*').join('\\s{0,1}')
+    .split('\\s+').join('\\s')
   const index = R++
   debug(name, index, value)
   t[name] = index
   src[index] = value
   re[index] = new RegExp(value, isGlobal ? 'g' : undefined)
+  safeRe[index] = new RegExp(safe, isGlobal ? 'g' : undefined)
 }

 // The following Regular Expressions can be used for tokenizing,
diff --git a/deps/npm/node_modules/semver/package.json b/deps/npm/node_modules/semver/package.json
index 7898f59..d8ae619 100644
--- a/deps/npm/node_modules/semver/package.json
+++ b/deps/npm/node_modules/semver/package.json
@@ -40,7 +40,7 @@
     "range.bnf"
   ],
   "tap": {
-    "check-coverage": true,
+    "timeout": 30,
     "coverage-map": "map.js"
   },
   "engines": {
--
2.40.0
