nvd-json_feeds 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/.document +3 -0
  3. data/.github/workflows/ruby.yml +29 -0
  4. data/.gitignore +9 -0
  5. data/.rspec +1 -0
  6. data/.yardopts +1 -0
  7. data/ChangeLog.md +25 -0
  8. data/Gemfile +13 -0
  9. data/LICENSE.txt +20 -0
  10. data/README.md +136 -0
  11. data/Rakefile +31 -0
  12. data/gemspec.yml +22 -0
  13. data/lib/nvd/json_feeds.rb +25 -0
  14. data/lib/nvd/json_feeds/exceptions.rb +15 -0
  15. data/lib/nvd/json_feeds/feed.rb +50 -0
  16. data/lib/nvd/json_feeds/feed_file.rb +95 -0
  17. data/lib/nvd/json_feeds/feed_uri.rb +131 -0
  18. data/lib/nvd/json_feeds/gz_feed_file.rb +60 -0
  19. data/lib/nvd/json_feeds/gz_feed_uri.rb +25 -0
  20. data/lib/nvd/json_feeds/json_feed_file.rb +21 -0
  21. data/lib/nvd/json_feeds/meta.rb +122 -0
  22. data/lib/nvd/json_feeds/meta_feed_uri.rb +22 -0
  23. data/lib/nvd/json_feeds/schema/configurations.rb +61 -0
  24. data/lib/nvd/json_feeds/schema/configurations/node.rb +98 -0
  25. data/lib/nvd/json_feeds/schema/cpe/has_uri.rb +66 -0
  26. data/lib/nvd/json_feeds/schema/cpe/match.rb +117 -0
  27. data/lib/nvd/json_feeds/schema/cpe/name.rb +67 -0
  28. data/lib/nvd/json_feeds/schema/cve_feed.rb +142 -0
  29. data/lib/nvd/json_feeds/schema/cve_item.rb +94 -0
  30. data/lib/nvd/json_feeds/schema/cvss_v2.rb +298 -0
  31. data/lib/nvd/json_feeds/schema/cvss_v3.rb +332 -0
  32. data/lib/nvd/json_feeds/schema/has_data_version.rb +54 -0
  33. data/lib/nvd/json_feeds/schema/impact.rb +73 -0
  34. data/lib/nvd/json_feeds/schema/impact/base_metric_v2.rb +132 -0
  35. data/lib/nvd/json_feeds/schema/impact/base_metric_v3.rb +79 -0
  36. data/lib/nvd/json_feeds/schema/timestamp.rb +9 -0
  37. data/lib/nvd/json_feeds/version.rb +6 -0
  38. data/lib/nvd/json_feeds/zip_feed_file.rb +64 -0
  39. data/lib/nvd/json_feeds/zip_feed_uri.rb +25 -0
  40. data/nvd-json_feeds.gemspec +61 -0
  41. data/spec/feed_file_examples.rb +27 -0
  42. data/spec/feed_file_spec.rb +42 -0
  43. data/spec/feed_spec.rb +56 -0
  44. data/spec/feed_uri_spec.rb +81 -0
  45. data/spec/fixtures/gz_feed_file/nvdcve-1.1-recent.json.gz +0 -0
  46. data/spec/fixtures/nvdcve-1.1-recent.json +180 -0
  47. data/spec/fixtures/zip_feed_file/nvdcve-1.1-recent.json.zip +0 -0
  48. data/spec/gz_feed_file_spec.rb +66 -0
  49. data/spec/gz_feed_uri_spec.rb +35 -0
  50. data/spec/json_feed_file_spec.rb +18 -0
  51. data/spec/json_feeds_spec.rb +8 -0
  52. data/spec/meta_spec.rb +141 -0
  53. data/spec/schema/configurations/node_spec.rb +87 -0
  54. data/spec/schema/configurations_spec.rb +57 -0
  55. data/spec/schema/cpe/match_spec.rb +188 -0
  56. data/spec/schema/cpe/name_spec.rb +54 -0
  57. data/spec/schema/cve_feed_spec.rb +162 -0
  58. data/spec/schema/cve_item_spec.rb +116 -0
  59. data/spec/schema/impact/base_metric_v2_spec.rb +183 -0
  60. data/spec/schema/impact/base_metric_v3_spec.rb +80 -0
  61. data/spec/schema/impact_spec.rb +53 -0
  62. data/spec/schema/shared_examples.rb +136 -0
  63. data/spec/schema/timestamp_spec.rb +8 -0
  64. data/spec/spec_helper.rb +8 -0
  65. data/spec/zip_feed_file_spec.rb +66 -0
  66. data/spec/zip_feed_uri_spec.rb +35 -0
  67. 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