purl 1.0.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CONTRIBUTING.md +167 -0
- data/README.md +159 -23
- data/Rakefile +171 -0
- data/SECURITY.md +164 -0
- data/lib/purl/errors.rb +56 -2
- data/lib/purl/package_url.rb +148 -2
- data/lib/purl/registry_url.rb +274 -40
- data/lib/purl/version.rb +1 -1
- data/lib/purl.rb +143 -5
- data/purl-types.json +242 -17
- data/schemas/purl-types.schema.json +154 -0
- data/schemas/test-suite-data.schema.json +134 -0
- metadata +9 -2
data/SECURITY.md
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
# Security Policy
|
|
2
|
+
|
|
3
|
+
## Supported Versions
|
|
4
|
+
|
|
5
|
+
We actively support and provide security updates for the following versions:
|
|
6
|
+
|
|
7
|
+
| Version | Supported |
|
|
8
|
+
| ------- | ------------------ |
|
|
9
|
+
| 1.x.x | :white_check_mark: |
|
|
10
|
+
| < 1.0 | :x: |
|
|
11
|
+
|
|
12
|
+
## Reporting a Vulnerability
|
|
13
|
+
|
|
14
|
+
The Purl team takes security seriously. If you discover a security vulnerability, please follow these steps:
|
|
15
|
+
|
|
16
|
+
### 1. Do NOT Create a Public Issue
|
|
17
|
+
|
|
18
|
+
Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.
|
|
19
|
+
|
|
20
|
+
### 2. Report Privately
|
|
21
|
+
|
|
22
|
+
Send a detailed report to **andrew@ecosyste.ms** with:
|
|
23
|
+
|
|
24
|
+
- **Subject**: `[SECURITY] Purl Ruby - [Brief Description]`
|
|
25
|
+
- **Description** of the vulnerability
|
|
26
|
+
- **Steps to reproduce** the issue
|
|
27
|
+
- **Potential impact** assessment
|
|
28
|
+
- **Suggested fix** (if you have one)
|
|
29
|
+
- **Your contact information** for follow-up
|
|
30
|
+
|
|
31
|
+
### 3. What to Include
|
|
32
|
+
|
|
33
|
+
Please provide as much information as possible:
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
- Affected versions
|
|
37
|
+
- Attack vectors
|
|
38
|
+
- Proof of concept (if safe to share)
|
|
39
|
+
- Environmental details (Ruby version, OS, etc.)
|
|
40
|
+
- Any relevant configuration details
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Response Process
|
|
44
|
+
|
|
45
|
+
### Initial Response
|
|
46
|
+
|
|
47
|
+
- **24-48 hours**: We will acknowledge receipt of your report
|
|
48
|
+
- **Initial assessment**: Within 1 week of acknowledgment
|
|
49
|
+
- **Status updates**: Weekly until resolution
|
|
50
|
+
|
|
51
|
+
### Investigation
|
|
52
|
+
|
|
53
|
+
We will:
|
|
54
|
+
1. **Confirm** the vulnerability exists
|
|
55
|
+
2. **Assess** the severity and impact
|
|
56
|
+
3. **Develop** a fix and mitigation strategy
|
|
57
|
+
4. **Test** the fix thoroughly
|
|
58
|
+
5. **Coordinate** disclosure timeline
|
|
59
|
+
|
|
60
|
+
### Resolution
|
|
61
|
+
|
|
62
|
+
- **High/Critical**: Immediate fix and release
|
|
63
|
+
- **Medium**: Fix within 30 days
|
|
64
|
+
- **Low**: Fix in next regular release cycle
|
|
65
|
+
|
|
66
|
+
## Security Considerations
|
|
67
|
+
|
|
68
|
+
### Input Validation
|
|
69
|
+
|
|
70
|
+
The Purl library processes Package URL strings and performs:
|
|
71
|
+
|
|
72
|
+
- **Scheme validation**: Ensures proper `pkg:` prefix
|
|
73
|
+
- **Component parsing**: Validates type, namespace, name, version
|
|
74
|
+
- **URI encoding**: Handles percent-encoded characters
|
|
75
|
+
- **Qualifier parsing**: Processes key-value parameters
|
|
76
|
+
|
|
77
|
+
### Potential Risk Areas
|
|
78
|
+
|
|
79
|
+
Areas that warrant security attention:
|
|
80
|
+
|
|
81
|
+
1. **URL Parsing**: Malformed URLs could cause parsing errors
|
|
82
|
+
2. **Regular Expressions**: Complex patterns may be vulnerable to ReDoS
|
|
83
|
+
3. **JSON Processing**: Configuration files require safe parsing
|
|
84
|
+
4. **Network Requests**: Registry URL generation involves external URLs
|
|
85
|
+
|
|
86
|
+
### Safe Usage Practices
|
|
87
|
+
|
|
88
|
+
When using Purl in applications:
|
|
89
|
+
|
|
90
|
+
- **Validate input**: Don't trust user-provided PURL strings
|
|
91
|
+
- **Handle errors**: Properly catch and handle parsing exceptions
|
|
92
|
+
- **Sanitize output**: Be careful when displaying parsed components
|
|
93
|
+
- **Rate limiting**: If parsing many PURLs, implement appropriate limits
|
|
94
|
+
|
|
95
|
+
## Disclosure Policy
|
|
96
|
+
|
|
97
|
+
### Coordinated Disclosure
|
|
98
|
+
|
|
99
|
+
We follow coordinated disclosure principles:
|
|
100
|
+
|
|
101
|
+
1. **Private reporting** allows us to fix issues before public disclosure
|
|
102
|
+
2. **Reasonable timeline** for fixes (typically 90 days maximum)
|
|
103
|
+
3. **Credit and recognition** for responsible reporters
|
|
104
|
+
4. **Public disclosure** after fixes are available
|
|
105
|
+
|
|
106
|
+
### Public Disclosure
|
|
107
|
+
|
|
108
|
+
After a fix is released:
|
|
109
|
+
|
|
110
|
+
1. **Security advisory** published on GitHub
|
|
111
|
+
2. **CVE requested** if applicable
|
|
112
|
+
3. **Release notes** include security information
|
|
113
|
+
4. **Community notification** through appropriate channels
|
|
114
|
+
|
|
115
|
+
## Security Updates
|
|
116
|
+
|
|
117
|
+
### Notification Channels
|
|
118
|
+
|
|
119
|
+
Security updates are announced through:
|
|
120
|
+
|
|
121
|
+
- **GitHub Security Advisories**
|
|
122
|
+
- **RubyGems security alerts**
|
|
123
|
+
- **Release notes and CHANGELOG**
|
|
124
|
+
- **Project README updates**
|
|
125
|
+
|
|
126
|
+
### Update Recommendations
|
|
127
|
+
|
|
128
|
+
To stay secure:
|
|
129
|
+
|
|
130
|
+
- **Monitor** our security advisories
|
|
131
|
+
- **Update regularly** to the latest version
|
|
132
|
+
- **Review** release notes for security fixes
|
|
133
|
+
- **Subscribe** to GitHub notifications for this repository
|
|
134
|
+
|
|
135
|
+
## Bug Bounty
|
|
136
|
+
|
|
137
|
+
Currently, we do not offer a formal bug bounty program. However, we deeply appreciate security researchers who help improve the project's security posture.
|
|
138
|
+
|
|
139
|
+
### Recognition
|
|
140
|
+
|
|
141
|
+
Contributors who responsibly disclose security issues will be:
|
|
142
|
+
|
|
143
|
+
- **Credited** in security advisories (with permission)
|
|
144
|
+
- **Mentioned** in release notes
|
|
145
|
+
- **Recognized** in project documentation
|
|
146
|
+
- **Thanked** publicly (unless anonymity is requested)
|
|
147
|
+
|
|
148
|
+
## Contact Information
|
|
149
|
+
|
|
150
|
+
**Security Contact**: andrew@ecosyste.ms
|
|
151
|
+
|
|
152
|
+
**PGP Key**: Available upon request for encrypted communications
|
|
153
|
+
|
|
154
|
+
**Response Time**: We aim to acknowledge security reports within 24-48 hours
|
|
155
|
+
|
|
156
|
+
## Additional Resources
|
|
157
|
+
|
|
158
|
+
- [PURL Specification Security Considerations](https://github.com/package-url/purl-spec)
|
|
159
|
+
- [Ruby Security Best Practices](https://guides.rubyonrails.org/security.html)
|
|
160
|
+
- [OWASP Secure Coding Practices](https://owasp.org/www-project-secure-coding-practices-quick-reference-guide/)
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
Thank you for helping keep Purl and its users safe!
|
data/lib/purl/errors.rb
CHANGED
|
@@ -5,9 +5,31 @@ module Purl
|
|
|
5
5
|
class Error < StandardError; end
|
|
6
6
|
|
|
7
7
|
# Validation errors for PURL components
|
|
8
|
+
#
|
|
9
|
+
# Contains additional context about which component failed validation
|
|
10
|
+
# and what rule was violated.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# begin
|
|
14
|
+
# PackageURL.new(type: "123invalid", name: "test")
|
|
15
|
+
# rescue ValidationError => e
|
|
16
|
+
# puts e.component # :type
|
|
17
|
+
# puts e.rule # "cannot start with number"
|
|
18
|
+
# end
|
|
8
19
|
class ValidationError < Error
|
|
9
|
-
|
|
20
|
+
# @return [Symbol, nil] the PURL component that failed validation
|
|
21
|
+
attr_reader :component
|
|
22
|
+
|
|
23
|
+
# @return [Object, nil] the value that failed validation
|
|
24
|
+
attr_reader :value
|
|
25
|
+
|
|
26
|
+
# @return [String, nil] the validation rule that was violated
|
|
27
|
+
attr_reader :rule
|
|
10
28
|
|
|
29
|
+
# @param message [String] error message
|
|
30
|
+
# @param component [Symbol, nil] component that failed validation
|
|
31
|
+
# @param value [Object, nil] value that failed validation
|
|
32
|
+
# @param rule [String, nil] validation rule that was violated
|
|
11
33
|
def initialize(message, component: nil, value: nil, rule: nil)
|
|
12
34
|
super(message)
|
|
13
35
|
@component = component
|
|
@@ -19,40 +41,71 @@ module Purl
|
|
|
19
41
|
# Parsing errors for malformed PURL strings
|
|
20
42
|
class ParseError < Error; end
|
|
21
43
|
|
|
22
|
-
# Specific validation errors
|
|
44
|
+
# Specific validation errors for PURL components
|
|
45
|
+
|
|
46
|
+
# Raised when a PURL type is invalid
|
|
23
47
|
class InvalidTypeError < ValidationError; end
|
|
48
|
+
|
|
49
|
+
# Raised when a PURL name is invalid
|
|
24
50
|
class InvalidNameError < ValidationError; end
|
|
51
|
+
|
|
52
|
+
# Raised when a PURL namespace is invalid
|
|
25
53
|
class InvalidNamespaceError < ValidationError; end
|
|
54
|
+
|
|
55
|
+
# Raised when a PURL qualifier is invalid
|
|
26
56
|
class InvalidQualifierError < ValidationError; end
|
|
57
|
+
|
|
58
|
+
# Raised when a PURL version is invalid
|
|
27
59
|
class InvalidVersionError < ValidationError; end
|
|
60
|
+
|
|
61
|
+
# Raised when a PURL subpath is invalid
|
|
28
62
|
class InvalidSubpathError < ValidationError; end
|
|
29
63
|
|
|
30
64
|
# Parsing-specific errors
|
|
65
|
+
|
|
66
|
+
# Raised when a PURL string doesn't start with "pkg:"
|
|
31
67
|
class InvalidSchemeError < ParseError; end
|
|
68
|
+
|
|
69
|
+
# Raised when a PURL string is malformed
|
|
32
70
|
class MalformedUrlError < ParseError; end
|
|
33
71
|
|
|
34
72
|
# Registry URL generation errors
|
|
73
|
+
#
|
|
74
|
+
# Contains additional context about which type caused the error.
|
|
35
75
|
class RegistryError < Error
|
|
76
|
+
# @return [String, nil] the PURL type that caused the error
|
|
36
77
|
attr_reader :type
|
|
37
78
|
|
|
79
|
+
# @param message [String] error message
|
|
80
|
+
# @param type [String, nil] PURL type that caused the error
|
|
38
81
|
def initialize(message, type: nil)
|
|
39
82
|
super(message)
|
|
40
83
|
@type = type
|
|
41
84
|
end
|
|
42
85
|
end
|
|
43
86
|
|
|
87
|
+
# Raised when trying to generate registry URLs for unsupported types
|
|
44
88
|
class UnsupportedTypeError < RegistryError
|
|
89
|
+
# @return [Array<String>] list of supported types
|
|
45
90
|
attr_reader :supported_types
|
|
46
91
|
|
|
92
|
+
# @param message [String] error message
|
|
93
|
+
# @param type [String, nil] unsupported type
|
|
94
|
+
# @param supported_types [Array<String>] list of supported types
|
|
47
95
|
def initialize(message, type: nil, supported_types: [])
|
|
48
96
|
super(message, type: type)
|
|
49
97
|
@supported_types = supported_types
|
|
50
98
|
end
|
|
51
99
|
end
|
|
52
100
|
|
|
101
|
+
# Raised when required registry information is missing
|
|
53
102
|
class MissingRegistryInfoError < RegistryError
|
|
103
|
+
# @return [String, nil] the missing information (e.g., "namespace")
|
|
54
104
|
attr_reader :missing
|
|
55
105
|
|
|
106
|
+
# @param message [String] error message
|
|
107
|
+
# @param type [String, nil] PURL type
|
|
108
|
+
# @param missing [String, nil] what information is missing
|
|
56
109
|
def initialize(message, type: nil, missing: nil)
|
|
57
110
|
super(message, type: type)
|
|
58
111
|
@missing = missing
|
|
@@ -60,5 +113,6 @@ module Purl
|
|
|
60
113
|
end
|
|
61
114
|
|
|
62
115
|
# Legacy compatibility - matches packageurl-ruby's exception name
|
|
116
|
+
# @deprecated Use {ParseError} instead
|
|
63
117
|
InvalidPackageURL = ParseError
|
|
64
118
|
end
|
data/lib/purl/package_url.rb
CHANGED
|
@@ -3,12 +3,74 @@
|
|
|
3
3
|
require "uri"
|
|
4
4
|
|
|
5
5
|
module Purl
|
|
6
|
+
# Represents a Package URL (PURL) - a mostly universal standard to reference
|
|
7
|
+
# a software package in a uniform way across many tools, programming languages
|
|
8
|
+
# and ecosystems.
|
|
9
|
+
#
|
|
10
|
+
# A PURL has the following components:
|
|
11
|
+
# - +type+: the package type (e.g., "gem", "npm", "maven")
|
|
12
|
+
# - +namespace+: optional namespace/scope (e.g., "@babel" for npm)
|
|
13
|
+
# - +name+: the package name (required)
|
|
14
|
+
# - +version+: optional version
|
|
15
|
+
# - +qualifiers+: optional key-value pairs
|
|
16
|
+
# - +subpath+: optional path within the package
|
|
17
|
+
#
|
|
18
|
+
# @example Creating a PackageURL
|
|
19
|
+
# purl = PackageURL.new(
|
|
20
|
+
# type: "gem",
|
|
21
|
+
# name: "rails",
|
|
22
|
+
# version: "7.0.0"
|
|
23
|
+
# )
|
|
24
|
+
# puts purl.to_s # "pkg:gem/rails@7.0.0"
|
|
25
|
+
#
|
|
26
|
+
# @example Parsing a PURL string
|
|
27
|
+
# purl = PackageURL.parse("pkg:npm/@babel/core@7.0.0")
|
|
28
|
+
# puts purl.namespace # "@babel"
|
|
29
|
+
# puts purl.name # "core"
|
|
30
|
+
#
|
|
31
|
+
# @see https://github.com/package-url/purl-spec PURL Specification
|
|
6
32
|
class PackageURL
|
|
7
|
-
|
|
33
|
+
# @return [String] the package type (e.g., "gem", "npm", "maven")
|
|
34
|
+
attr_reader :type
|
|
35
|
+
|
|
36
|
+
# @return [String, nil] the package namespace/scope
|
|
37
|
+
attr_reader :namespace
|
|
38
|
+
|
|
39
|
+
# @return [String] the package name
|
|
40
|
+
attr_reader :name
|
|
41
|
+
|
|
42
|
+
# @return [String, nil] the package version
|
|
43
|
+
attr_reader :version
|
|
44
|
+
|
|
45
|
+
# @return [Hash<String, String>, nil] key-value qualifier pairs
|
|
46
|
+
attr_reader :qualifiers
|
|
47
|
+
|
|
48
|
+
# @return [String, nil] subpath within the package
|
|
49
|
+
attr_reader :subpath
|
|
8
50
|
|
|
9
51
|
VALID_TYPE_CHARS = /\A[a-zA-Z0-9\.\+\-]+\z/
|
|
10
52
|
VALID_QUALIFIER_KEY_CHARS = /\A[a-zA-Z0-9\.\-_]+\z/
|
|
11
53
|
|
|
54
|
+
# Create a new PackageURL instance
|
|
55
|
+
#
|
|
56
|
+
# @param type [String, Symbol] the package type (required)
|
|
57
|
+
# @param name [String] the package name (required)
|
|
58
|
+
# @param namespace [String, nil] optional namespace/scope
|
|
59
|
+
# @param version [String, nil] optional version
|
|
60
|
+
# @param qualifiers [Hash, nil] optional key-value qualifier pairs
|
|
61
|
+
# @param subpath [String, nil] optional subpath within package
|
|
62
|
+
#
|
|
63
|
+
# @raise [InvalidTypeError] if type is invalid
|
|
64
|
+
# @raise [InvalidNameError] if name is invalid
|
|
65
|
+
# @raise [ValidationError] if any component fails type-specific validation
|
|
66
|
+
#
|
|
67
|
+
# @example
|
|
68
|
+
# purl = PackageURL.new(
|
|
69
|
+
# type: "npm",
|
|
70
|
+
# namespace: "@babel",
|
|
71
|
+
# name: "core",
|
|
72
|
+
# version: "7.0.0"
|
|
73
|
+
# )
|
|
12
74
|
def initialize(type:, name:, namespace: nil, version: nil, qualifiers: nil, subpath: nil)
|
|
13
75
|
@type = validate_and_normalize_type(type)
|
|
14
76
|
@name = validate_name(name)
|
|
@@ -21,6 +83,25 @@ module Purl
|
|
|
21
83
|
validate_type_specific_rules
|
|
22
84
|
end
|
|
23
85
|
|
|
86
|
+
# Parse a PURL string into a PackageURL object
|
|
87
|
+
#
|
|
88
|
+
# @param purl_string [String] PURL string starting with "pkg:"
|
|
89
|
+
# @return [PackageURL] parsed package URL object
|
|
90
|
+
# @raise [InvalidSchemeError] if string doesn't start with "pkg:"
|
|
91
|
+
# @raise [MalformedUrlError] if string is malformed
|
|
92
|
+
# @raise [ValidationError] if parsed components fail validation
|
|
93
|
+
#
|
|
94
|
+
# @example Basic parsing
|
|
95
|
+
# purl = PackageURL.parse("pkg:gem/rails@7.0.0")
|
|
96
|
+
# puts purl.type # "gem"
|
|
97
|
+
# puts purl.name # "rails"
|
|
98
|
+
# puts purl.version # "7.0.0"
|
|
99
|
+
#
|
|
100
|
+
# @example Complex parsing with all components
|
|
101
|
+
# purl = PackageURL.parse("pkg:npm/@babel/core@7.0.0?arch=x64#lib/index.js")
|
|
102
|
+
# puts purl.namespace # "@babel"
|
|
103
|
+
# puts purl.qualifiers # {"arch" => "x64"}
|
|
104
|
+
# puts purl.subpath # "lib/index.js"
|
|
24
105
|
def self.parse(purl_string)
|
|
25
106
|
raise InvalidSchemeError, "PURL must start with 'pkg:'" unless purl_string.start_with?("pkg:")
|
|
26
107
|
|
|
@@ -134,6 +215,13 @@ module Purl
|
|
|
134
215
|
)
|
|
135
216
|
end
|
|
136
217
|
|
|
218
|
+
# Convert the PackageURL to its canonical string representation
|
|
219
|
+
#
|
|
220
|
+
# @return [String] canonical PURL string
|
|
221
|
+
#
|
|
222
|
+
# @example
|
|
223
|
+
# purl = PackageURL.new(type: "gem", name: "rails", version: "7.0.0")
|
|
224
|
+
# puts purl.to_s # "pkg:gem/rails@7.0.0"
|
|
137
225
|
def to_s
|
|
138
226
|
result = "pkg:#{type.downcase}"
|
|
139
227
|
|
|
@@ -183,6 +271,15 @@ module Purl
|
|
|
183
271
|
result
|
|
184
272
|
end
|
|
185
273
|
|
|
274
|
+
# Convert the PackageURL to a hash representation
|
|
275
|
+
#
|
|
276
|
+
# @return [Hash<Symbol, Object>] hash with component keys and values
|
|
277
|
+
#
|
|
278
|
+
# @example
|
|
279
|
+
# purl = PackageURL.new(type: "gem", name: "rails", version: "7.0.0")
|
|
280
|
+
# hash = purl.to_h
|
|
281
|
+
# # => {:type=>"gem", :namespace=>nil, :name=>"rails", :version=>"7.0.0",
|
|
282
|
+
# # :qualifiers=>nil, :subpath=>nil}
|
|
186
283
|
def to_h
|
|
187
284
|
{
|
|
188
285
|
type: type,
|
|
@@ -194,26 +291,75 @@ module Purl
|
|
|
194
291
|
}
|
|
195
292
|
end
|
|
196
293
|
|
|
294
|
+
# Compare two PackageURL objects for equality
|
|
295
|
+
#
|
|
296
|
+
# Two PURLs are equal if their canonical string representations are identical.
|
|
297
|
+
#
|
|
298
|
+
# @param other [Object] object to compare with
|
|
299
|
+
# @return [Boolean] true if equal, false otherwise
|
|
300
|
+
#
|
|
301
|
+
# @example
|
|
302
|
+
# purl1 = PackageURL.parse("pkg:gem/rails@7.0.0")
|
|
303
|
+
# purl2 = PackageURL.parse("pkg:gem/rails@7.0.0")
|
|
304
|
+
# puts purl1 == purl2 # true
|
|
197
305
|
def ==(other)
|
|
198
306
|
return false unless other.is_a?(PackageURL)
|
|
199
307
|
|
|
200
308
|
to_s == other.to_s
|
|
201
309
|
end
|
|
202
310
|
|
|
311
|
+
# Generate hash code for the PackageURL
|
|
312
|
+
#
|
|
313
|
+
# @return [Integer] hash code based on canonical string representation
|
|
203
314
|
def hash
|
|
204
315
|
to_s.hash
|
|
205
316
|
end
|
|
206
317
|
|
|
207
318
|
# Pattern matching support for Ruby 2.7+
|
|
319
|
+
#
|
|
320
|
+
# Allows destructuring PackageURL in pattern matching.
|
|
321
|
+
#
|
|
322
|
+
# @return [Array] array of [type, namespace, name, version, qualifiers, subpath]
|
|
323
|
+
#
|
|
324
|
+
# @example Ruby 2.7+ pattern matching
|
|
325
|
+
# case purl
|
|
326
|
+
# in ["gem", nil, name, version, nil, nil]
|
|
327
|
+
# puts "Simple gem: #{name} v#{version}"
|
|
328
|
+
# end
|
|
208
329
|
def deconstruct
|
|
209
330
|
[type, namespace, name, version, qualifiers, subpath]
|
|
210
331
|
end
|
|
211
332
|
|
|
333
|
+
# Pattern matching support for Ruby 2.7+ (hash patterns)
|
|
334
|
+
#
|
|
335
|
+
# @param keys [Array<Symbol>, nil] keys to extract, or nil for all keys
|
|
336
|
+
# @return [Hash<Symbol, Object>] hash with requested keys
|
|
337
|
+
#
|
|
338
|
+
# @example Ruby 2.7+ hash pattern matching
|
|
339
|
+
# case purl
|
|
340
|
+
# in {type: "gem", name:, version:}
|
|
341
|
+
# puts "Gem #{name} version #{version}"
|
|
342
|
+
# end
|
|
212
343
|
def deconstruct_keys(keys)
|
|
213
|
-
to_h.slice(*keys) if keys
|
|
344
|
+
return to_h.slice(*keys) if keys
|
|
214
345
|
to_h
|
|
215
346
|
end
|
|
216
347
|
|
|
348
|
+
# Create a new PackageURL with modified attributes
|
|
349
|
+
#
|
|
350
|
+
# @param changes [Hash] attributes to change
|
|
351
|
+
# @return [PackageURL] new PackageURL instance with changes applied
|
|
352
|
+
#
|
|
353
|
+
# @example
|
|
354
|
+
# purl = PackageURL.parse("pkg:gem/rails@7.0.0")
|
|
355
|
+
# new_purl = purl.with(version: "7.1.0", qualifiers: {"arch" => "x64"})
|
|
356
|
+
# puts new_purl.to_s # "pkg:gem/rails@7.1.0?arch=x64"
|
|
357
|
+
def with(**changes)
|
|
358
|
+
current_attrs = to_h
|
|
359
|
+
new_attrs = current_attrs.merge(changes)
|
|
360
|
+
self.class.new(**new_attrs)
|
|
361
|
+
end
|
|
362
|
+
|
|
217
363
|
private
|
|
218
364
|
|
|
219
365
|
def validate_and_normalize_type(type)
|