From 325ba9e90ba54c84b2a99c8358a9c412e2baa883 Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 26 Nov 2024 00:13:13 +0100 Subject: [PATCH 1/3] Ignore platform-specific gems This fixes an issue in the Ruby `Gemfile.lock` parser where version-specific dependencies like `ffi (1.17.0-x86_64-linux-gnu)` would cause the parser to crash. Since new Ruby Gems are not allowed to contain dashes, this patch simply discards Gems that contain a `-` in their version. There are technically two versions of `asciidoctor-reducer` (`1.0.0-rc.1` and `1.0.0-beta.1`) which contain dashes, but those are neither commonly used, nor a security risk. Closes #1540. --- lockfile/src/parsers/gem.rs | 17 +++++++++-------- lockfile/src/ruby.rs | 7 ++++++- tests/fixtures/Gemfile.lock | 5 ++++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/lockfile/src/parsers/gem.rs b/lockfile/src/parsers/gem.rs index c755131e4..a79739a35 100644 --- a/lockfile/src/parsers/gem.rs +++ b/lockfile/src/parsers/gem.rs @@ -1,7 +1,7 @@ use nom::branch::alt; use nom::bytes::complete::{tag, take_until}; use nom::character::complete::{line_ending, not_line_ending, satisfy, space0}; -use nom::combinator::{opt, recognize}; +use nom::combinator::{eof, opt, recognize}; use nom::error::{VerboseError, VerboseErrorKind}; use nom::multi::{many1, many_till}; use nom::sequence::{delimited, tuple}; @@ -15,10 +15,10 @@ use crate::{Package, PackageVersion, ThirdPartyVersion}; const DEFAULT_REGISTRY: &str = "https://rubygems.org/"; /// Legal non-alphanumeric characters in loose version specifications. -const LOOSE_VERSION_CHARS: &[char] = &[' ', ',', '<', '>', '=', '~', '!', '.', '-', '+']; +const LOOSE_VERSION_CHARS: &[char] = &[' ', ',', '<', '>', '=', '~', '!', '.', '-', '+', '_']; /// Legal non-alphanumeric characters in strict version specifications. -const STRICT_VERSION_CHARS: &[char] = &['.', '-', '+']; +const STRICT_VERSION_CHARS: &[char] = &['.']; #[derive(Debug)] struct Section<'a> { @@ -224,7 +224,7 @@ fn package_name(input: &str) -> IResult<&str, &str> { } /// Parser allowing for loose `(>= 1.2.0, < 2.0, != 1.2.3)` and strict -/// `(1.2.3-alpha+build3)` versions. +/// `(1.2.3.alpha.1)` versions. fn loose_package_version(input: &str) -> IResult<&str, &str> { // Versions can be completely omitted for sub-dependencies. if input.is_empty() { @@ -241,12 +241,13 @@ fn loose_package_version(input: &str) -> IResult<&str, &str> { )(input) } -/// Parser allowing only strict `1.2.3-alpha+build3` versions. +/// Parser allowing only strict `1.2.3.alpha.1` versions. fn strict_package_version(input: &str) -> IResult<&str, &str> { let (input, _) = space0(input)?; - recognize(many1(satisfy(|c: char| { - c.is_ascii_alphanumeric() || STRICT_VERSION_CHARS.contains(&c) - })))(input) + recognize(tuple(( + many1(satisfy(|c: char| c.is_ascii_alphanumeric() || STRICT_VERSION_CHARS.contains(&c))), + eof, + )))(input) } /// Get the value for a key in a ` key: value` line. diff --git a/lockfile/src/ruby.rs b/lockfile/src/ruby.rs index 2570d0a0b..92c1d6245 100644 --- a/lockfile/src/ruby.rs +++ b/lockfile/src/ruby.rs @@ -48,7 +48,7 @@ mod tests { #[test] fn lock_parse_gem() { let pkgs = GemLock.parse(include_str!("../../tests/fixtures/Gemfile.lock")).unwrap(); - assert_eq!(pkgs.len(), 11); + assert_eq!(pkgs.len(), 12); let expected_pkgs = [ Package { @@ -84,6 +84,11 @@ mod tests { }), package_type: PackageType::RubyGems, }, + Package { + name: "ffi".into(), + version: PackageVersion::FirstParty("1.17.0".into()), + package_type: PackageType::RubyGems, + }, ]; for expected_pkg in expected_pkgs { diff --git a/tests/fixtures/Gemfile.lock b/tests/fixtures/Gemfile.lock index 875eeef9d..f024f3288 100644 --- a/tests/fixtures/Gemfile.lock +++ b/tests/fixtures/Gemfile.lock @@ -31,7 +31,7 @@ GEM rspec-core (~> 3.11.0) rspec-expectations (~> 3.11.0) rspec-mocks (~> 3.11.0) - rspec-core (3.11.0-alpha+build3) + rspec-core (3.11.0) rspec-support (~> 3.11.0) rspec-expectations (3.11.1) diff-lcs (>= 1.2.0, < 2.0) @@ -42,12 +42,15 @@ GEM rspec-support (3.11.1) PLATFORMS + ruby x86_64-linux GEM remote: https://rubygems.org/ specs: wirble (0.1.3) + ffi (1.17.0) + ffi (1.17.0-x86_64-linux-gnu) DEPENDENCIES benchmark! From cf0f18e61a313ef91d926c655d5c06fac6ca403e Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 26 Nov 2024 00:31:11 +0100 Subject: [PATCH 2/3] Fix dependencies only present with a suffix --- lockfile/src/parsers/gem.rs | 9 ++++----- lockfile/src/ruby.rs | 17 ++++++++++++++--- tests/fixtures/Gemfile.lock | 2 +- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lockfile/src/parsers/gem.rs b/lockfile/src/parsers/gem.rs index a79739a35..dd7305111 100644 --- a/lockfile/src/parsers/gem.rs +++ b/lockfile/src/parsers/gem.rs @@ -1,7 +1,7 @@ use nom::branch::alt; use nom::bytes::complete::{tag, take_until}; use nom::character::complete::{line_ending, not_line_ending, satisfy, space0}; -use nom::combinator::{eof, opt, recognize}; +use nom::combinator::{opt, recognize}; use nom::error::{VerboseError, VerboseErrorKind}; use nom::multi::{many1, many_till}; use nom::sequence::{delimited, tuple}; @@ -244,10 +244,9 @@ fn loose_package_version(input: &str) -> IResult<&str, &str> { /// Parser allowing only strict `1.2.3.alpha.1` versions. fn strict_package_version(input: &str) -> IResult<&str, &str> { let (input, _) = space0(input)?; - recognize(tuple(( - many1(satisfy(|c: char| c.is_ascii_alphanumeric() || STRICT_VERSION_CHARS.contains(&c))), - eof, - )))(input) + recognize(many1(satisfy(|c: char| { + c.is_ascii_alphanumeric() || STRICT_VERSION_CHARS.contains(&c) + })))(input) } /// Get the value for a key in a ` key: value` line. diff --git a/lockfile/src/ruby.rs b/lockfile/src/ruby.rs index 92c1d6245..e3942a4eb 100644 --- a/lockfile/src/ruby.rs +++ b/lockfile/src/ruby.rs @@ -17,11 +17,17 @@ pub struct GemLock; impl Parse for GemLock { /// Parses `Gemfile.lock` files into a vec of packages fn parse(&self, data: &str) -> anyhow::Result> { - let (_, entries) = gem::parse(data) + let (_, mut packages) = gem::parse(data) .finish() .map_err(|e| anyhow!(convert_error(data, e))) .context("Failed to parse gem lockfile")?; - Ok(entries) + + // Remove duplicate dependencies, which can occur when a dependency is included + // with multiple different platform suffixes. + packages.sort_unstable(); + packages.dedup(); + + Ok(packages) } fn is_path_lockfile(&self, path: &Path) -> bool { @@ -48,7 +54,7 @@ mod tests { #[test] fn lock_parse_gem() { let pkgs = GemLock.parse(include_str!("../../tests/fixtures/Gemfile.lock")).unwrap(); - assert_eq!(pkgs.len(), 12); + assert_eq!(pkgs.len(), 13); let expected_pkgs = [ Package { @@ -89,6 +95,11 @@ mod tests { version: PackageVersion::FirstParty("1.17.0".into()), package_type: PackageType::RubyGems, }, + Package { + name: "fake".into(), + version: PackageVersion::FirstParty("1.2.3".into()), + package_type: PackageType::RubyGems, + }, ]; for expected_pkg in expected_pkgs { diff --git a/tests/fixtures/Gemfile.lock b/tests/fixtures/Gemfile.lock index f024f3288..1eb534946 100644 --- a/tests/fixtures/Gemfile.lock +++ b/tests/fixtures/Gemfile.lock @@ -42,7 +42,6 @@ GEM rspec-support (3.11.1) PLATFORMS - ruby x86_64-linux GEM @@ -51,6 +50,7 @@ GEM wirble (0.1.3) ffi (1.17.0) ffi (1.17.0-x86_64-linux-gnu) + fake (1.2.3-x86_64-linux-gnu) DEPENDENCIES benchmark! From 4c2f337da3aa83d147bdb7dca1af3d677795c50a Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 26 Nov 2024 00:33:09 +0100 Subject: [PATCH 3/3] Add changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d95f1f489..74893c39a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - `pnpm` version 5 parser including metadata in package versions +- Platform-specific dependencies ignored by the `Gemfile.lock` parser ## 7.1.4 - 2024-11-07