nvd-json_feeds 0.1.0
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 +7 -0
- data/.document +3 -0
- data/.github/workflows/ruby.yml +29 -0
- data/.gitignore +9 -0
- data/.rspec +1 -0
- data/.yardopts +1 -0
- data/ChangeLog.md +25 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +20 -0
- data/README.md +136 -0
- data/Rakefile +31 -0
- data/gemspec.yml +22 -0
- data/lib/nvd/json_feeds.rb +25 -0
- data/lib/nvd/json_feeds/exceptions.rb +15 -0
- data/lib/nvd/json_feeds/feed.rb +50 -0
- data/lib/nvd/json_feeds/feed_file.rb +95 -0
- data/lib/nvd/json_feeds/feed_uri.rb +131 -0
- data/lib/nvd/json_feeds/gz_feed_file.rb +60 -0
- data/lib/nvd/json_feeds/gz_feed_uri.rb +25 -0
- data/lib/nvd/json_feeds/json_feed_file.rb +21 -0
- data/lib/nvd/json_feeds/meta.rb +122 -0
- data/lib/nvd/json_feeds/meta_feed_uri.rb +22 -0
- data/lib/nvd/json_feeds/schema/configurations.rb +61 -0
- data/lib/nvd/json_feeds/schema/configurations/node.rb +98 -0
- data/lib/nvd/json_feeds/schema/cpe/has_uri.rb +66 -0
- data/lib/nvd/json_feeds/schema/cpe/match.rb +117 -0
- data/lib/nvd/json_feeds/schema/cpe/name.rb +67 -0
- data/lib/nvd/json_feeds/schema/cve_feed.rb +142 -0
- data/lib/nvd/json_feeds/schema/cve_item.rb +94 -0
- data/lib/nvd/json_feeds/schema/cvss_v2.rb +298 -0
- data/lib/nvd/json_feeds/schema/cvss_v3.rb +332 -0
- data/lib/nvd/json_feeds/schema/has_data_version.rb +54 -0
- data/lib/nvd/json_feeds/schema/impact.rb +73 -0
- data/lib/nvd/json_feeds/schema/impact/base_metric_v2.rb +132 -0
- data/lib/nvd/json_feeds/schema/impact/base_metric_v3.rb +79 -0
- data/lib/nvd/json_feeds/schema/timestamp.rb +9 -0
- data/lib/nvd/json_feeds/version.rb +6 -0
- data/lib/nvd/json_feeds/zip_feed_file.rb +64 -0
- data/lib/nvd/json_feeds/zip_feed_uri.rb +25 -0
- data/nvd-json_feeds.gemspec +61 -0
- data/spec/feed_file_examples.rb +27 -0
- data/spec/feed_file_spec.rb +42 -0
- data/spec/feed_spec.rb +56 -0
- data/spec/feed_uri_spec.rb +81 -0
- data/spec/fixtures/gz_feed_file/nvdcve-1.1-recent.json.gz +0 -0
- data/spec/fixtures/nvdcve-1.1-recent.json +180 -0
- data/spec/fixtures/zip_feed_file/nvdcve-1.1-recent.json.zip +0 -0
- data/spec/gz_feed_file_spec.rb +66 -0
- data/spec/gz_feed_uri_spec.rb +35 -0
- data/spec/json_feed_file_spec.rb +18 -0
- data/spec/json_feeds_spec.rb +8 -0
- data/spec/meta_spec.rb +141 -0
- data/spec/schema/configurations/node_spec.rb +87 -0
- data/spec/schema/configurations_spec.rb +57 -0
- data/spec/schema/cpe/match_spec.rb +188 -0
- data/spec/schema/cpe/name_spec.rb +54 -0
- data/spec/schema/cve_feed_spec.rb +162 -0
- data/spec/schema/cve_item_spec.rb +116 -0
- data/spec/schema/impact/base_metric_v2_spec.rb +183 -0
- data/spec/schema/impact/base_metric_v3_spec.rb +80 -0
- data/spec/schema/impact_spec.rb +53 -0
- data/spec/schema/shared_examples.rb +136 -0
- data/spec/schema/timestamp_spec.rb +8 -0
- data/spec/spec_helper.rb +8 -0
- data/spec/zip_feed_file_spec.rb +66 -0
- data/spec/zip_feed_uri_spec.rb +35 -0
- metadata +156 -0
@@ -0,0 +1,131 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/https'
|
4
|
+
|
5
|
+
module NVD
|
6
|
+
module JSONFeeds
|
7
|
+
#
|
8
|
+
# Base class for all feed URIs.
|
9
|
+
#
|
10
|
+
class FeedURI
|
11
|
+
|
12
|
+
SCHEMA_VERSION = '1.1'
|
13
|
+
|
14
|
+
BASE_URI = "https://nvd.nist.gov/feeds/json/cve/#{SCHEMA_VERSION}"
|
15
|
+
|
16
|
+
# The feed name or year.
|
17
|
+
#
|
18
|
+
# @return [:modified, :recent, Integer]
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
# The feed file extension.
|
22
|
+
#
|
23
|
+
# @return [String]
|
24
|
+
attr_reader :ext
|
25
|
+
|
26
|
+
# The file name of the feed.
|
27
|
+
#
|
28
|
+
# @return [String]
|
29
|
+
attr_reader :filename
|
30
|
+
|
31
|
+
# The feed URI.
|
32
|
+
#
|
33
|
+
# @return [URI::HTTPS]
|
34
|
+
attr_reader :uri
|
35
|
+
|
36
|
+
#
|
37
|
+
# Initializes the feed URI.
|
38
|
+
#
|
39
|
+
# @param [:modified, :recent, Integer] name
|
40
|
+
# The feed name or year.
|
41
|
+
#
|
42
|
+
# @param [String] ext
|
43
|
+
# The feed file extension (ex: `.json.gz`).
|
44
|
+
#
|
45
|
+
def initialize(name,ext)
|
46
|
+
@name = name
|
47
|
+
@ext = ext
|
48
|
+
|
49
|
+
@filename = "nvdcve-#{SCHEMA_VERSION}-#{@name}#{@ext}"
|
50
|
+
@uri = URI("#{BASE_URI}/#{@filename}")
|
51
|
+
end
|
52
|
+
|
53
|
+
#
|
54
|
+
# Performs and HTTP GET request to the feed URI.
|
55
|
+
#
|
56
|
+
# @yield [chunk]
|
57
|
+
# If a block is given, it will be passed each chunk read from the
|
58
|
+
# response.
|
59
|
+
#
|
60
|
+
# @yieldparam [String] chunk
|
61
|
+
# A chunk of data read from the response body.
|
62
|
+
#
|
63
|
+
# @return [String]
|
64
|
+
# If no block is given, the full response body will be returned.
|
65
|
+
#
|
66
|
+
def get(&block)
|
67
|
+
if block
|
68
|
+
Net::HTTP.start(@uri.host,@uri.port, use_ssl: true) do |http|
|
69
|
+
request = Net::HTTP::Get.new(uri)
|
70
|
+
|
71
|
+
http.request(request) do |response|
|
72
|
+
response.read_body(&block)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
else
|
76
|
+
Net::HTTP.get(@uri)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
#
|
81
|
+
# Downloads the feed to the given destination.
|
82
|
+
#
|
83
|
+
# @param [String] dest
|
84
|
+
# Either a destination file or directory.
|
85
|
+
#
|
86
|
+
def download(dest)
|
87
|
+
dest_path = if File.directory?(dest) then File.join(dest,@filename)
|
88
|
+
else dest
|
89
|
+
end
|
90
|
+
|
91
|
+
File.open(dest_path,'w') do |file|
|
92
|
+
get do |chunk|
|
93
|
+
file.write(chunk)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
return dest_path
|
98
|
+
end
|
99
|
+
|
100
|
+
#
|
101
|
+
# Converts the feed URI to a regular URI.
|
102
|
+
#
|
103
|
+
# @return [URI::HTTPS]
|
104
|
+
# The feed URI.
|
105
|
+
#
|
106
|
+
def to_uri
|
107
|
+
@uri
|
108
|
+
end
|
109
|
+
|
110
|
+
#
|
111
|
+
# Converts the feed URI to a String.
|
112
|
+
#
|
113
|
+
# @return [String]
|
114
|
+
# The String version of the feed URI.
|
115
|
+
#
|
116
|
+
def to_s
|
117
|
+
@uri.to_s
|
118
|
+
end
|
119
|
+
|
120
|
+
#
|
121
|
+
# Inspects the feed URI.
|
122
|
+
#
|
123
|
+
# @return [String]
|
124
|
+
#
|
125
|
+
def inspect
|
126
|
+
"#<#{self.class}: #{self}>"
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'nvd/json_feeds/feed_file'
|
4
|
+
require 'nvd/json_feeds/json_feed_file'
|
5
|
+
require 'nvd/json_feeds/exceptions'
|
6
|
+
|
7
|
+
require 'shellwords'
|
8
|
+
|
9
|
+
module NVD
|
10
|
+
module JSONFeeds
|
11
|
+
class GzFeedFile < FeedFile
|
12
|
+
|
13
|
+
#
|
14
|
+
# The filename of the `.json` file within the `.gz` archive.
|
15
|
+
#
|
16
|
+
# @return [String]
|
17
|
+
#
|
18
|
+
def json_filename
|
19
|
+
File.basename(@path,'.gz')
|
20
|
+
end
|
21
|
+
|
22
|
+
#
|
23
|
+
# Reads the compressed feed file.
|
24
|
+
#
|
25
|
+
# @return [String]
|
26
|
+
#
|
27
|
+
# @raise [ReadFailed]
|
28
|
+
# The `gunzip` command is not installed.
|
29
|
+
#
|
30
|
+
def read
|
31
|
+
`gunzip -q -c -k #{Shellwords.escape(@path)}`
|
32
|
+
rescue Errno::ENOENT
|
33
|
+
raise(ReadFailed,"gunzip command is not installed")
|
34
|
+
end
|
35
|
+
|
36
|
+
#
|
37
|
+
# Extracts the feed file.
|
38
|
+
#
|
39
|
+
# @return [JSONFeedFile]
|
40
|
+
#
|
41
|
+
# @raise [ExtractFailed]
|
42
|
+
# The `gunzip` command failed or did the `.json` file was not extracted.
|
43
|
+
#
|
44
|
+
def extract
|
45
|
+
unless system('gunzip', '-q', '-k', @path)
|
46
|
+
raise(ExtractFailed,"gunzip command failed")
|
47
|
+
end
|
48
|
+
|
49
|
+
extracted_dir = File.dirname(@path)
|
50
|
+
extracted_path = File.join(extracted_dir,json_filename)
|
51
|
+
|
52
|
+
unless File.file?(extracted_path)
|
53
|
+
raise(ExtractFailed,"extraction failed: #{@path.inspect}")
|
54
|
+
end
|
55
|
+
|
56
|
+
return JSONFeedFile.new(extracted_path)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'nvd/json_feeds/feed_uri'
|
2
|
+
require 'nvd/json_feeds/gz_feed_file'
|
3
|
+
|
4
|
+
module NVD
|
5
|
+
module JSONFeeds
|
6
|
+
#
|
7
|
+
# Represents a URI to a `.json.gz` feed file.
|
8
|
+
#
|
9
|
+
class GzFeedURI < FeedURI
|
10
|
+
|
11
|
+
#
|
12
|
+
# Downloads the compressed feed file.
|
13
|
+
#
|
14
|
+
# @param [String] dest
|
15
|
+
# The destination file or directory.
|
16
|
+
#
|
17
|
+
# @return [GzFeedFile]
|
18
|
+
#
|
19
|
+
def download(dest)
|
20
|
+
GzFeedFile.new(super(dest))
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'nvd/json_feeds/feed_file'
|
2
|
+
|
3
|
+
module NVD
|
4
|
+
module JSONFeeds
|
5
|
+
#
|
6
|
+
# Represents a `.json` feed file.
|
7
|
+
#
|
8
|
+
class JSONFeedFile < FeedFile
|
9
|
+
|
10
|
+
#
|
11
|
+
# Reads the JSON feed file.
|
12
|
+
#
|
13
|
+
# @return [String]
|
14
|
+
#
|
15
|
+
def read
|
16
|
+
File.read(@path)
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'nvd/json_feeds/exceptions'
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
|
5
|
+
module NVD
|
6
|
+
module JSONFeeds
|
7
|
+
#
|
8
|
+
# Represents the parsed contents of a `.meta` file.
|
9
|
+
#
|
10
|
+
class Meta
|
11
|
+
|
12
|
+
# The date the feed file was last updated.
|
13
|
+
#
|
14
|
+
# @return [DateTime]
|
15
|
+
attr_reader :last_modified_date
|
16
|
+
|
17
|
+
# The size of the uncompressed `.json` feed file.
|
18
|
+
#
|
19
|
+
# @return [Integer]
|
20
|
+
attr_reader :size
|
21
|
+
|
22
|
+
# The size of the `.zip` feed file.
|
23
|
+
#
|
24
|
+
# @return [Integer]
|
25
|
+
attr_reader :zip_size
|
26
|
+
|
27
|
+
# The size of the `.gz` feed file.
|
28
|
+
#
|
29
|
+
# @return [Integer]
|
30
|
+
attr_reader :gz_size
|
31
|
+
|
32
|
+
# The SHA256 checksum of the uncompressed `.json` feed file.
|
33
|
+
#
|
34
|
+
# @return [String]
|
35
|
+
# @note NVD uses all upper-case SHA256 checksums.
|
36
|
+
attr_reader :sha256
|
37
|
+
|
38
|
+
#
|
39
|
+
# Initializes the feed metadata.
|
40
|
+
#
|
41
|
+
# @param [DateTime] last_modified_date
|
42
|
+
# The parsed `lastModifiedDate` timestamp.
|
43
|
+
#
|
44
|
+
# @param [Integer] size
|
45
|
+
# The `size` value.
|
46
|
+
#
|
47
|
+
# @param [Integer] zip_size
|
48
|
+
# The `zipSize` value.
|
49
|
+
#
|
50
|
+
# @param [Integer] gz_size
|
51
|
+
# The `gzSize` value.
|
52
|
+
#
|
53
|
+
# @param [String] sha256
|
54
|
+
# The `sha256` value.
|
55
|
+
#
|
56
|
+
def initialize(last_modified_date,size,zip_size,gz_size,sha256)
|
57
|
+
@last_modified_date = last_modified_date
|
58
|
+
|
59
|
+
@size = size
|
60
|
+
@zip_size = zip_size
|
61
|
+
@gz_size = gz_size
|
62
|
+
@sha256 = sha256
|
63
|
+
end
|
64
|
+
|
65
|
+
#
|
66
|
+
# Parses the text.
|
67
|
+
#
|
68
|
+
# @param [String] string
|
69
|
+
# The raw meta string.
|
70
|
+
#
|
71
|
+
# @return [Meta]
|
72
|
+
# The parsed meta information.
|
73
|
+
#
|
74
|
+
# @raise [MetaParseError]
|
75
|
+
# The meta string was malformed.
|
76
|
+
#
|
77
|
+
def self.parse(string)
|
78
|
+
last_modified_date = nil
|
79
|
+
size = nil
|
80
|
+
zip_size = nil
|
81
|
+
gz_size = nil
|
82
|
+
sha256 = nil
|
83
|
+
|
84
|
+
string.each_line do |line|
|
85
|
+
name, value = line.chomp.split(':',2)
|
86
|
+
|
87
|
+
case name
|
88
|
+
when 'lastModifiedDate'
|
89
|
+
last_modified_date = DateTime.parse(value)
|
90
|
+
when 'size' then size = value.to_i
|
91
|
+
when 'zipSize' then zip_size = value.to_i
|
92
|
+
when 'gzSize' then gz_size = value.to_i
|
93
|
+
when 'sha256' then sha256 = value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
unless last_modified_date
|
98
|
+
raise(MetaParseError,"lastModifiedDate missing")
|
99
|
+
end
|
100
|
+
|
101
|
+
unless size
|
102
|
+
raise(MetaParseError,"size entry missing")
|
103
|
+
end
|
104
|
+
|
105
|
+
unless zip_size
|
106
|
+
raise(MetaParseError,"zipSize entry missing")
|
107
|
+
end
|
108
|
+
|
109
|
+
unless gz_size
|
110
|
+
raise(MetaParseError,"gzSize entry missing")
|
111
|
+
end
|
112
|
+
|
113
|
+
unless sha256
|
114
|
+
raise(MetaParseError,"sha256 entry missing")
|
115
|
+
end
|
116
|
+
|
117
|
+
return new(last_modified_date,size,zip_size,gz_size,sha256)
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'nvd/json_feeds/feed_uri'
|
2
|
+
require 'nvd/json_feeds/meta'
|
3
|
+
|
4
|
+
module NVD
|
5
|
+
module JSONFeeds
|
6
|
+
#
|
7
|
+
# Represents a URI to the `.meta` feed file.
|
8
|
+
#
|
9
|
+
class MetaFeedURI < FeedURI
|
10
|
+
|
11
|
+
#
|
12
|
+
# Requests and parses the feed metadata.
|
13
|
+
#
|
14
|
+
# @return [FeedMeta]
|
15
|
+
#
|
16
|
+
def parse
|
17
|
+
Meta.parse(get)
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
require 'nvd/json_feeds/schema/configurations/node'
|
2
|
+
require 'nvd/json_feeds/schema/has_data_version'
|
3
|
+
|
4
|
+
module NVD
|
5
|
+
module JSONFeeds
|
6
|
+
module Schema
|
7
|
+
#
|
8
|
+
# Represents the `"configurations"` value.
|
9
|
+
#
|
10
|
+
class Configurations
|
11
|
+
|
12
|
+
include HasDataVersion
|
13
|
+
|
14
|
+
# The nodes of the configuration.
|
15
|
+
#
|
16
|
+
# @return [Array<Node>]
|
17
|
+
attr_reader :nodes
|
18
|
+
|
19
|
+
#
|
20
|
+
# Initializes the configuration.
|
21
|
+
#
|
22
|
+
# @param [Array<Node>] nodes
|
23
|
+
#
|
24
|
+
def initialize(nodes: [], **kwargs)
|
25
|
+
super(**kwargs)
|
26
|
+
|
27
|
+
@nodes = nodes
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Maps the parsed JSON to a Symbol Hash for {#initialize}.
|
32
|
+
#
|
33
|
+
# @param [Hash{String => Object}] json
|
34
|
+
#
|
35
|
+
# @return [Hash{Symbol => Object}]
|
36
|
+
#
|
37
|
+
def self.from_json(json)
|
38
|
+
{
|
39
|
+
**super(json),
|
40
|
+
|
41
|
+
nodes: Array(json['nodes']).map(&Node.method(:load))
|
42
|
+
}
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Loads the configuration from the parsed JSON.
|
47
|
+
#
|
48
|
+
# @param [Hash{String => Object}] json
|
49
|
+
# The parsed JSON.
|
50
|
+
#
|
51
|
+
# @return [Cojnfiguration]
|
52
|
+
# The loaded configuration object.
|
53
|
+
#
|
54
|
+
def self.load(json)
|
55
|
+
new(**from_json(json))
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|