From 6f46153e09e00e230d728a5b5108a0aa0429533f Mon Sep 17 00:00:00 2001
From: Rhys Arkins <rhys@arkins.net>
Date: Sun, 1 Mar 2020 20:52:43 +0100
Subject: [PATCH] fix(auto-replace): use regex replace to avoid infinite loops

---
 lib/workers/branch/auto-replace.ts       | 43 +++++++++++++++---------
 test/workers/branch/auto-replace.spec.ts |  6 ----
 2 files changed, 27 insertions(+), 22 deletions(-)

diff --git a/lib/workers/branch/auto-replace.ts b/lib/workers/branch/auto-replace.ts
index 710ec4b2e4..8724207ef6 100644
--- a/lib/workers/branch/auto-replace.ts
+++ b/lib/workers/branch/auto-replace.ts
@@ -2,6 +2,7 @@ import { logger } from '../../logger';
 import { get } from '../../manager';
 import { WORKER_FILE_UPDATE_FAILED } from '../../constants/error-messages';
 import { matchAt, replaceAt } from '../../util/string';
+import { regEx } from '../../util/regex';
 
 export async function confirmIfDepUpdated(
   upgrade,
@@ -59,6 +60,10 @@ export async function checkBranchDepsMatchBaseDeps(
   }
 }
 
+function escapeRegExp(input: string): string {
+  return input.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string
+}
+
 export async function doAutoReplace(
   upgrade,
   existingContent: string,
@@ -77,27 +82,33 @@ export async function doAutoReplace(
     return existingContent;
   }
   const {
+    depName,
     currentValue,
     newValue,
     currentDigest,
     newDigest,
-    autoReplaceData,
+    autoReplaceData = {},
   } = upgrade;
   const replaceString = autoReplaceData.replaceString || currentValue;
+  logger.trace({ depName, replaceString }, 'autoReplace replaceString');
+  let searchIndex = existingContent.indexOf(replaceString);
+  if (searchIndex === -1) {
+    logger.error(
+      { depName },
+      'Cannot find replaceString in current file content'
+    );
+    throw new Error(WORKER_FILE_UPDATE_FAILED);
+  }
   try {
-    let newString = replaceString;
-    do {
-      newString = newString.replace(currentValue, newValue);
-    } while (newString.includes(currentValue));
-    if (currentDigest) {
-      do {
-        newString = newString.replace(currentDigest, newDigest);
-      } while (newString.includes(currentDigest));
-    }
-    let searchIndex = existingContent.indexOf(replaceString);
-    if (searchIndex === -1) {
-      logger.error('Cannot find replaceString in current file content');
-      throw new Error(WORKER_FILE_UPDATE_FAILED);
+    let newString = replaceString.replace(
+      regEx(escapeRegExp(currentValue), 'g'),
+      newValue
+    );
+    if (currentDigest && newDigest) {
+      newString = newString.replace(
+        regEx(escapeRegExp(currentDigest), 'g'),
+        newDigest
+      );
     }
     logger.debug(`Starting search at index ${searchIndex}`);
     // Iterate through the rest of the file
@@ -117,9 +128,9 @@ export async function doAutoReplace(
         }
       }
     }
-  } catch (err) {
+  } catch (err) /* istanbul ignore next */ {
     logger.debug({ err }, 'doAutoReplace error');
   }
-  logger.error('Could not autoReplace');
+  // istanbul ignore next
   throw new Error(WORKER_FILE_UPDATE_FAILED);
 }
diff --git a/test/workers/branch/auto-replace.spec.ts b/test/workers/branch/auto-replace.spec.ts
index 61997a6e0e..b81f8b79e5 100644
--- a/test/workers/branch/auto-replace.spec.ts
+++ b/test/workers/branch/auto-replace.spec.ts
@@ -21,11 +21,6 @@ describe('workers/branch/auto-replace', () => {
       };
       parentBranch = undefined;
     });
-    it('throws on error', async () => {
-      await expect(
-        doAutoReplace(upgrade, 'existing content', parentBranch)
-      ).rejects.toThrow();
-    });
     it('rebases if the deps list has changed', async () => {
       upgrade.baseDeps = extractPackageFile(sampleHtml).deps;
       parentBranch = 'some existing branch';
@@ -54,7 +49,6 @@ describe('workers/branch/auto-replace', () => {
       upgrade.newValue = '7.1.1';
       upgrade.autoReplaceData = {
         depIndex: 0,
-        replaceString: script,
       };
       const res = await doAutoReplace(upgrade, src, parentBranch);
       expect(res).toMatchSnapshot();
-- 
GitLab