From af817f9e8081960024a96e1f9b25bf0e648d352c Mon Sep 17 00:00:00 2001
From: Matt Palmer <9059517+56KBs@users.noreply.github.com>
Date: Fri, 17 Dec 2021 12:47:45 +0000
Subject: [PATCH] fix(go): Mimic Go logic for `GOPRIVATE` parsing (#13166)

Ensure that the parsing of `GOPRIVATE`/`GONOPROXY` matches the behaviour of Go itself.

The documentation for these values state it matches the logic of `path.Match`, however in reality it's actually a prefix based match.

Updating the regex to allow for either an exact match, or a match where the configured value is a prefix of the package, when a `/` is added.

Additionally, strip any trailing `/`'s from the configured value, as this matches the logic that Go takes when matching.

Fixes #13138
---
 lib/datasource/go/releases-goproxy.spec.ts | 63 +++++++++++++++++-----
 lib/datasource/go/releases-goproxy.ts      |  8 ++-
 2 files changed, 58 insertions(+), 13 deletions(-)

diff --git a/lib/datasource/go/releases-goproxy.spec.ts b/lib/datasource/go/releases-goproxy.spec.ts
index e6ec16ca38..3dc555a74d 100644
--- a/lib/datasource/go/releases-goproxy.spec.ts
+++ b/lib/datasource/go/releases-goproxy.spec.ts
@@ -98,22 +98,31 @@ describe('datasource/go/releases-goproxy', () => {
       expect(parseNoproxy(undefined)).toBeNull();
       expect(parseNoproxy(null)).toBeNull();
       expect(parseNoproxy('')).toBeNull();
-      expect(parseNoproxy('*')?.source).toBe('^(?:[^\\/]*)$');
-      expect(parseNoproxy('?')?.source).toBe('^(?:[^\\/])$');
-      expect(parseNoproxy('foo')?.source).toBe('^(?:foo)$');
-      expect(parseNoproxy('\\f\\o\\o')?.source).toBe('^(?:foo)$');
-      expect(parseNoproxy('foo,bar')?.source).toBe('^(?:foo|bar)$');
-      expect(parseNoproxy('[abc]')?.source).toBe('^(?:[abc])$');
-      expect(parseNoproxy('[a-c]')?.source).toBe('^(?:[a-c])$');
-      expect(parseNoproxy('[\\a-\\c]')?.source).toBe('^(?:[a-c])$');
-      expect(parseNoproxy('a.b.c')?.source).toBe('^(?:a\\.b\\.c)$');
+      expect(parseNoproxy('/')).toBeNull();
+      expect(parseNoproxy('*')?.source).toBe('^(?:[^\\/]*)(?:\\/.*)?$');
+      expect(parseNoproxy('?')?.source).toBe('^(?:[^\\/])(?:\\/.*)?$');
+      expect(parseNoproxy('foo')?.source).toBe('^(?:foo)(?:\\/.*)?$');
+      expect(parseNoproxy('\\f\\o\\o')?.source).toBe('^(?:foo)(?:\\/.*)?$');
+      expect(parseNoproxy('foo,bar')?.source).toBe('^(?:foo|bar)(?:\\/.*)?$');
+      expect(parseNoproxy('[abc]')?.source).toBe('^(?:[abc])(?:\\/.*)?$');
+      expect(parseNoproxy('[a-c]')?.source).toBe('^(?:[a-c])(?:\\/.*)?$');
+      expect(parseNoproxy('[\\a-\\c]')?.source).toBe('^(?:[a-c])(?:\\/.*)?$');
+      expect(parseNoproxy('a.b.c')?.source).toBe('^(?:a\\.b\\.c)(?:\\/.*)?$');
+      expect(parseNoproxy('trailing/')?.source).toBe(
+        '^(?:trailing)(?:\\/.*)?$'
+      );
     });
 
     it('matches on real package prefixes', () => {
+      expect(parseNoproxy('ex.co').test('ex.co/foo')).toBeTrue();
+      expect(parseNoproxy('ex.co/').test('ex.co/foo')).toBeTrue();
+      expect(parseNoproxy('ex.co/foo/bar').test('ex.co/foo/bar')).toBeTrue();
       expect(parseNoproxy('ex.co/foo/bar').test('ex.co/foo/bar')).toBeTrue();
       expect(parseNoproxy('*/foo/*').test('example.com/foo/bar')).toBeTrue();
       expect(parseNoproxy('ex.co/foo/*').test('ex.co/foo/bar')).toBeTrue();
       expect(parseNoproxy('ex.co/foo/*').test('ex.co/foo/baz')).toBeTrue();
+      expect(parseNoproxy('ex.co').test('ex.co/foo/v2')).toBeTrue();
+
       expect(
         parseNoproxy('ex.co/foo/bar,ex.co/foo/baz').test('ex.co/foo/bar')
       ).toBeTrue();
@@ -123,15 +132,45 @@ describe('datasource/go/releases-goproxy', () => {
       expect(
         parseNoproxy('ex.co/foo/bar,ex.co/foo/baz').test('ex.co/foo/qux')
       ).toBeFalse();
-    });
 
-    it('Matches from start to end', () => {
-      expect(parseNoproxy('x').test('x/aba')).toBeFalse();
+      expect(parseNoproxy('ex').test('ex.co/foo')).toBeFalse();
+
       expect(parseNoproxy('aba').test('x/aba')).toBeFalse();
       expect(parseNoproxy('x/b').test('x/aba')).toBeFalse();
       expect(parseNoproxy('x/ab').test('x/aba')).toBeFalse();
       expect(parseNoproxy('x/ab[a-b]').test('x/aba')).toBeTrue();
     });
+
+    it('matches on wildcards', () => {
+      expect(parseNoproxy('/*/').test('ex.co/foo')).toBeFalse();
+      expect(parseNoproxy('*/foo').test('ex.co/foo')).toBeTrue();
+      expect(parseNoproxy('*/fo').test('ex.co/foo')).toBeFalse();
+      expect(parseNoproxy('*/fo?').test('ex.co/foo')).toBeTrue();
+      expect(parseNoproxy('*/fo*').test('ex.co/foo')).toBeTrue();
+      expect(parseNoproxy('*fo*').test('ex.co/foo')).toBeFalse();
+
+      expect(parseNoproxy('*.co').test('ex.co/foo')).toBeTrue();
+      expect(parseNoproxy('ex*').test('ex.co/foo')).toBeTrue();
+      expect(parseNoproxy('*/foo').test('ex.co/foo/v2')).toBeTrue();
+      expect(parseNoproxy('*/foo/').test('ex.co/foo/v2')).toBeTrue();
+      expect(parseNoproxy('*/foo/*').test('ex.co/foo/v2')).toBeTrue();
+      expect(parseNoproxy('*/foo/*/').test('ex.co/foo/v2')).toBeTrue();
+      expect(parseNoproxy('*/v2').test('ex.co/foo/v2')).toBeFalse();
+      expect(parseNoproxy('*/*/v2').test('ex.co/foo/v2')).toBeTrue();
+      expect(parseNoproxy('*/*/*').test('ex.co/foo/v2')).toBeTrue();
+      expect(parseNoproxy('*/*/*/').test('ex.co/foo/v2')).toBeTrue();
+      expect(parseNoproxy('*/*/*').test('ex.co/foo')).toBeFalse();
+      expect(parseNoproxy('*/*/*/').test('ex.co/foo')).toBeFalse();
+
+      expect(parseNoproxy('*/*/*,,').test('ex.co/repo')).toBeFalse();
+      expect(parseNoproxy('*/*/*,,*/repo').test('ex.co/repo')).toBeTrue();
+      expect(parseNoproxy(',,*/repo').test('ex.co/repo')).toBeTrue();
+    });
+
+    it('matches on character ranges', () => {
+      expect(parseNoproxy('x/ab[a-b]').test('x/aba')).toBeTrue();
+      expect(parseNoproxy('x/ab[a-b]').test('x/abc')).toBeFalse();
+    });
   });
 
   describe('getReleases', () => {
diff --git a/lib/datasource/go/releases-goproxy.ts b/lib/datasource/go/releases-goproxy.ts
index c35de8224f..2546e88e7e 100644
--- a/lib/datasource/go/releases-goproxy.ts
+++ b/lib/datasource/go/releases-goproxy.ts
@@ -72,6 +72,10 @@ const lexer = moo.states({
       push: 'characterRange',
       value: (_: string) => '[',
     },
+    trailingSlash: {
+      match: /\/$/,
+      value: (_: string) => '',
+    },
     char: {
       match: /[^*?\\[\n]/,
       value: (s: string) => s.replace(regEx('\\.', 'g'), '\\.'),
@@ -107,7 +111,9 @@ export function parseNoproxy(
   }
   lexer.reset(input);
   const noproxyPattern = [...lexer].map(({ value }) => value).join('');
-  const result = noproxyPattern ? regEx(`^(?:${noproxyPattern})$`) : null;
+  const result = noproxyPattern
+    ? regEx(`^(?:${noproxyPattern})(?:/.*)?$`)
+    : null;
   parsedNoproxy[input] = result;
   return result;
 }
-- 
GitLab