nvd-json_feeds 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|