Repository: google/osv-scanner
Pull request #381 (2023-05-14 14:07:26)

fix(deps): update osv-scanner minor
[![Mend Renovate](https://app.renovatebot.com/images/banner.svg)](https://renovatebot.com)

This PR contains the following updates:

| Package | Type | Update | Change |
|---|---|---|---|
| golang.org/x/exp | require | digest | `47ecfdc` -> `dd950f8` |
| golang.org/x/tools | require | minor | `v0.8.0` -> `v0.9.1` |

---

### Configuration

📅 **Schedule**: Branch creation - "before 6am on monday" in timezone Australia/Sydney, Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://togithub.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://www.mend.io/free-developer-tools/renovate/). View repository job log [here](https://app.renovatebot.com/dashboard#github/google/osv-scanner).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiIzNS40OC4yIiwidXBkYXRlZEluVmVyIjoiMzUuNDguMiJ9-->



Patch:
diff --git a/go.mod b/go.mod
index d92433e..b1bf3ca 100644
--- a/go.mod
+++ b/go.mod
@@ -13,11 +13,11 @@ require (
 	github.com/package-url/packageurl-go v0.1.0
 	github.com/spdx/tools-golang v0.5.0
 	github.com/urfave/cli/v2 v2.25.3
-	golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53
+	golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea
 	golang.org/x/mod v0.10.0
 	golang.org/x/sync v0.2.0
 	golang.org/x/term v0.8.0
-	golang.org/x/tools v0.8.0
+	golang.org/x/tools v0.9.1
 	golang.org/x/vuln v0.0.0-20230303230808-d3042fecc4e3
 	gopkg.in/yaml.v3 v3.0.1
 )
@@ -46,7 +46,7 @@ require (
 	github.com/xanzy/ssh-agent v0.3.3 // indirect
 	github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect
 	golang.org/x/crypto v0.6.0 // indirect
-	golang.org/x/net v0.9.0 // indirect
+	golang.org/x/net v0.10.0 // indirect
 	golang.org/x/sys v0.8.0 // indirect
 	gopkg.in/warnings.v0 v0.1.2 // indirect
 )
diff --git a/go.sum b/go.sum
index 957ca74..e6a5e20 100644
--- a/go.sum
+++ b/go.sum
@@ -117,8 +117,8 @@ golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0
 golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
 golang.org/x/crypto v0.6.0 h1:qfktjS5LUO+fFKeJXZ+ikTRijMmljikvG68fpMMruSc=
 golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
-golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53 h1:5llv2sWeaMSnA3w2kS57ouQQ4pudlXrR0dCgw51QK9o=
-golang.org/x/exp v0.0.0-20230425010034-47ecfdc1ba53/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
+golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea h1:vLCWI/yYrdEHyN2JzIzPO3aaQJHQdp89IZBA/+azVC4=
+golang.org/x/exp v0.0.0-20230510235704-dd950f8aeaea/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
 golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 golang.org/x/mod v0.6.0/go.mod h1:4mET923SAdbXp2ki8ey+zGs1SLqsuM2Y0uvdZR/fUNI=
 golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
@@ -131,8 +131,8 @@ golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfS
 golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
 golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
 golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
-golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
-golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
+golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
+golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
 golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI=
@@ -174,8 +174,8 @@ golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGm
 golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 golang.org/x/tools v0.2.0/go.mod h1:y4OqIKeOV/fWJetJ8bXPU1sEVniLMIyDAZWeHdV+NTA=
-golang.org/x/tools v0.8.0 h1:vSDcovVPld282ceKgDimkRSC8kpaH1dgyc9UMzlt84Y=
-golang.org/x/tools v0.8.0/go.mod h1:JxBZ99ISMI5ViVkT1tr6tdNmXeTrcpVSD3vZ1RsRdN4=
+golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo=
+golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc=
 golang.org/x/vuln v0.0.0-20230303230808-d3042fecc4e3 h1:9GJsAwSzB/ztwMwsEm3ihUgCXHCULbNsubxqIrdKa44=
 golang.org/x/vuln v0.0.0-20230303230808-d3042fecc4e3/go.mod h1:LTLnfk/dpXDNKsX6aCg/cI4LyCVnTyrQhgV/yLJuly0=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
diff --git a/pkg/lockfile/fixtures/pip/line-continuation.txt b/pkg/lockfile/fixtures/pip/line-continuation.txt
new file mode 100644
index 0000000..af78779
--- /dev/null
+++ b/pkg/lockfile/fixtures/pip/line-continuation.txt
@@ -0,0 +1,17 @@
+# unescaped
+foo==\
+\
+ \
+  \
+1.2.3
+
+# escaped, a literal backslash for some reason
+bar == 4.5\\
+.6
+
+# comments are stripped only after line continuations are processed
+baz == 7.8.9 # \
+baz == 1.2.3
+
+# continue to end
+qux == 10.11.12\
diff --git a/pkg/lockfile/fixtures/pip/with-per-requirement-options.txt b/pkg/lockfile/fixtures/pip/with-per-requirement-options.txt
new file mode 100644
index 0000000..cf672f4
--- /dev/null
+++ b/pkg/lockfile/fixtures/pip/with-per-requirement-options.txt
@@ -0,0 +1,12 @@
+boto3==1.26.121 --hash=sha256:f87d694c351eba1dfd19b5bef5892a1047e7adb09c57c2c00049de209a8ab55d
+foo == 1.0.0
+
+# from https://pip.pypa.io/en/stable/topics/secure-installs/#hash-checking-mode
+
+FooProject == 1.2 \
+  --hash=sha256:2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824 \
+  --hash=sha256:486ea46224d1bb4fb680f34f7c9ad96a8f24ec88be73ea8e5a6c65260e9cb8a7
+
+# from https://pip.pypa.io/en/stable/reference/requirements-file-format/#influencing-the-build-system
+
+BarProject >= 1.2 --global-option="--no-user-cfg"
diff --git a/pkg/lockfile/parse-requirements-txt.go b/pkg/lockfile/parse-requirements-txt.go
index d7564a4..18e7b2f 100644
--- a/pkg/lockfile/parse-requirements-txt.go
+++ b/pkg/lockfile/parse-requirements-txt.go
@@ -38,12 +38,11 @@ func parseLine(line string) PackageDetails {
 	}
 
 	if constraint != "" {
-		splitted := strings.Split(line, constraint)
-
-		name = strings.TrimSpace(splitted[0])
+		unprocessedName, unprocessedVersion, _ := strings.Cut(line, constraint)
+		name = strings.TrimSpace(unprocessedName)
 
 		if constraint != "!=" {
-			version = strings.TrimSpace(splitted[1])
+			version, _, _ = strings.Cut(strings.TrimSpace(unprocessedVersion), " ")
 		}
 	}
 
@@ -70,7 +69,7 @@ func normalizedRequirementName(name string) string {
 	// per https://www.python.org/dev/peps/pep-0503/#normalized-names
 	name = cachedregexp.MustCompile(`[-_.]+`).ReplaceAllString(name, "-")
 	name = strings.ToLower(name)
-	name = strings.Split(name, "[")[0]
+	name, _, _ = strings.Cut(name, "[")
 
 	return name
 }
@@ -93,6 +92,14 @@ func isNotRequirementLine(line string) bool {
 		strings.HasPrefix(line, "/")
 }
 
+func isLineContinuation(line string) bool {
+	// checks that the line ends with an odd number of back slashes,
+	// meaning the last one isn't escaped
+	var re = cachedregexp.MustCompile(`([^\\]|^)(\\{2})*\\$`)
+
+	return re.MatchString(line)
+}
+
 func ParseRequirementsTxt(pathToLockfile string) ([]PackageDetails, error) {
 	return parseRequirementsTxt(pathToLockfile, map[string]struct{}{})
 }
@@ -106,9 +113,18 @@ func parseRequirementsTxt(pathToLockfile string, requiredAlready map[string]stru
 	defer file.Close()
 
 	scanner := bufio.NewScanner(file)
-
 	for scanner.Scan() {
-		line := removeComments(scanner.Text())
+		line := scanner.Text()
+
+		for isLineContinuation(line) {
+			line = strings.TrimSuffix(line, "\\")
+
+			if scanner.Scan() {
+				line += scanner.Text()
+			}
+		}
+
+		line = removeComments(line)
 
 		if ar := strings.TrimPrefix(line, "-r "); ar != line {
 			ar = filepath.Join(filepath.Dir(pathToLockfile), ar)


Test patch:
diff --git a/pkg/lockfile/parse-requirements-txt_test.go b/pkg/lockfile/parse-requirements-txt_test.go
index 845bb15..8206732 100644
--- a/pkg/lockfile/parse-requirements-txt_test.go
+++ b/pkg/lockfile/parse-requirements-txt_test.go
@@ -469,6 +469,7 @@ func TestParseRequirementsTxt_DuplicateROptions(t *testing.T) {
 		},
 	})
 }
+
 func TestParseRequirementsTxt_CyclicRSelf(t *testing.T) {
 	t.Parallel()
 
@@ -493,6 +494,7 @@ func TestParseRequirementsTxt_CyclicRSelf(t *testing.T) {
 		},
 	})
 }
+
 func TestParseRequirementsTxt_CyclicRComplex(t *testing.T) {
 	t.Parallel()
 
@@ -523,3 +525,77 @@ func TestParseRequirementsTxt_CyclicRComplex(t *testing.T) {
 		},
 	})
 }
+
+func TestParseRequirementsTxt_WithPerRequirementOptions(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRequirementsTxt("fixtures/pip/with-per-requirement-options.txt")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "boto3",
+			Version:   "1.26.121",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "foo",
+			Version:   "1.0.0",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "fooproject",
+			Version:   "1.2",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "barproject",
+			Version:   "1.2",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+	})
+}
+
+func TestParseRequirementsTxt_LineContinuation(t *testing.T) {
+	t.Parallel()
+
+	packages, err := lockfile.ParseRequirementsTxt("fixtures/pip/line-continuation.txt")
+
+	if err != nil {
+		t.Errorf("Got unexpected error: %v", err)
+	}
+
+	expectPackages(t, packages, []lockfile.PackageDetails{
+		{
+			Name:      "foo",
+			Version:   "1.2.3",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "bar",
+			Version:   "4.5\\\\",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "baz",
+			Version:   "7.8.9",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+		{
+			Name:      "qux",
+			Version:   "10.11.12",
+			Ecosystem: lockfile.PipEcosystem,
+			CompareAs: lockfile.PipEcosystem,
+		},
+	})
+}