nvd_feed_api 0.0.3 → 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: eede7e83299ed5178ac0ef1563ac49378b90bf6f
4
- data.tar.gz: 82f073efcb5f266a8d848e5e93a2e6969ecd01db
2
+ SHA256:
3
+ metadata.gz: 158649a33021b95393055afc6861185443aeae4665cc6acd86814f29a866d8f0
4
+ data.tar.gz: 75ca47cdcece6352581ed8ddfe9339ce393e48199ba4c895e6e49e05e959b08b
5
5
  SHA512:
6
- metadata.gz: e398a51fa724e9028aea8e6966a5b8620d34b9b7a0a20311259e810d99103a04eb0977db8aa46ce8edc4d70d2ee4049903a212309ea570cff752ce01defc37b3
7
- data.tar.gz: 235093a028f0bb61bf18879cb6dd3fe42e4e8e45b451ba8de41c6e697edadfc73bf10665ecdd3705ecc8e0220eb6b78edafef92cf22d63058bedc16ad6f6ed19
6
+ metadata.gz: 66c08b3167c12da6168331f84caa557d945ebb16c76e565cefa6ac44c7960e5eb8bae840a2231c16d2cc9ef982bd13eb9b68289dff66dbee4af0c115a45e20f4
7
+ data.tar.gz: 2729e066d04f9f29e9fd3fbd76c88cb3c208c2a82eab331e3202fca9a42a7f658d7645f7d3c17f5df770c54b1eb8e084b611f6e23eefab90c7e4cada90e114f0
data/.gitignore CHANGED
@@ -50,4 +50,4 @@ build-iPhoneSimulator/
50
50
  .rvmrc
51
51
 
52
52
  # do not check Gemfile.lock fror gems
53
- Gemfile.lock
53
+ #Gemfile.lock
@@ -1,36 +1,51 @@
1
1
  # Official language image. Look for the different tagged releases at:
2
2
  # https://hub.docker.com/r/library/ruby/tags/
3
- image: ruby:2.4-alpine
4
3
 
4
+ # Caching: https://docs.gitlab.com/ee/ci/caching/#caching-ruby-dependencies
5
5
  cache:
6
+ key: ${CI_COMMIT_REF_SLUG}
6
7
  paths:
7
8
  - vendor/ruby # cache gems in between builds
8
9
 
9
10
  before_script:
10
11
  - ruby -v # Print out ruby version for debugging
11
- - gem install bundler --no-ri --no-rdoc # Bundler is not installed with the image
12
+ - gem install bundler --no-document # Bundler is not installed with the image
12
13
  # install nproc (coreutils) for bundle -j
13
14
  # install git for building the gemspec
14
15
  # install make, gcc for building gem native extension (commonmarker)
15
16
  # libc-dev for musl-dev dependency (stdlib.h) needed by gcc
16
17
  - apk --no-cache add coreutils git make gcc libc-dev
17
18
  - bundle install -j $(nproc) --path vendor # Install dependencies into ./vendor/ruby
18
- - rake install # install the gem
19
+ - bundle exec rake install # install the gem
19
20
 
20
- rubocop:
21
+ # Anchors: https://docs.gitlab.com/ee/ci/yaml/README.html#anchors
22
+ .test_template: &job_definition
21
23
  stage: test
22
24
  script:
23
- - rubocop
25
+ - bundle exec rubocop
26
+ - bundle exec rake test
24
27
 
25
- test:
26
- stage: test
27
- script:
28
- - rake test
28
+ test:2.4:
29
+ <<: *job_definition
30
+ image: ruby:2.4-alpine
31
+
32
+ test:2.5:
33
+ <<: *job_definition
34
+ image: ruby:2.5-alpine
35
+
36
+ test:2.6:
37
+ <<: *job_definition
38
+ image: ruby:2.6-alpine
39
+
40
+ test:2.7:
41
+ <<: *job_definition
42
+ image: ruby:2.7-alpine
29
43
 
30
44
  pages:
31
45
  stage: deploy
46
+ image: ruby:2.4-alpine
32
47
  script:
33
- - yard doc
48
+ - bundle exec yard doc
34
49
  - mkdir public
35
50
  - mv doc/* public/
36
51
  artifacts:
@@ -1,5 +1,12 @@
1
1
  AllCops:
2
- TargetRubyVersion: 2.4
2
+ TargetRubyVersion: 2.7
3
+ NewCops: enable
4
+
5
+ Layout/HashAlignment:
6
+ EnforcedHashRocketStyle: table
7
+
8
+ Layout/LineLength:
9
+ Enabled: false
3
10
 
4
11
  # Rubocop is too stupid too see that the variable is used
5
12
  Lint/UselessAssignment:
@@ -18,10 +25,7 @@ Metrics/ClassLength:
18
25
  Enabled: false
19
26
 
20
27
  Metrics/CyclomaticComplexity:
21
- Max: 15
22
-
23
- Metrics/LineLength:
24
- Enabled: false
28
+ Max: 20
25
29
 
26
30
  Metrics/MethodLength:
27
31
  Max: 100
@@ -44,3 +48,6 @@ Style/PerlBackrefs:
44
48
  # Allow explicit return
45
49
  Style/RedundantReturn:
46
50
  Enabled: false
51
+
52
+ Style/WordArray:
53
+ EnforcedStyle: brackets
@@ -0,0 +1 @@
1
+ ruby 2.7.1
@@ -0,0 +1,67 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ nvd_feed_api (0.3.1)
5
+ archive-zip (~> 0.11)
6
+ nokogiri (~> 1.10)
7
+ oj (>= 3.7.8, < 4)
8
+
9
+ GEM
10
+ remote: https://rubygems.org/
11
+ specs:
12
+ archive-zip (0.12.0)
13
+ io-like (~> 0.3.0)
14
+ ast (2.4.1)
15
+ commonmarker (0.21.0)
16
+ ruby-enum (~> 0.5)
17
+ concurrent-ruby (1.1.7)
18
+ github-markup (3.0.4)
19
+ i18n (1.8.5)
20
+ concurrent-ruby (~> 1.0)
21
+ io-like (0.3.1)
22
+ mini_portile2 (2.4.0)
23
+ minitest (5.14.2)
24
+ nokogiri (1.10.10)
25
+ mini_portile2 (~> 2.4.0)
26
+ oj (3.10.14)
27
+ parallel (1.19.2)
28
+ parser (2.7.1.5)
29
+ ast (~> 2.4.1)
30
+ rainbow (3.0.0)
31
+ rake (13.0.1)
32
+ redcarpet (3.5.0)
33
+ regexp_parser (1.8.1)
34
+ rexml (3.2.4)
35
+ rubocop (0.92.0)
36
+ parallel (~> 1.10)
37
+ parser (>= 2.7.1.5)
38
+ rainbow (>= 2.2.2, < 4.0)
39
+ regexp_parser (>= 1.7)
40
+ rexml
41
+ rubocop-ast (>= 0.5.0)
42
+ ruby-progressbar (~> 1.7)
43
+ unicode-display_width (>= 1.4.0, < 2.0)
44
+ rubocop-ast (0.7.1)
45
+ parser (>= 2.7.1.5)
46
+ ruby-enum (0.8.0)
47
+ i18n
48
+ ruby-progressbar (1.10.1)
49
+ unicode-display_width (1.7.0)
50
+ yard (0.9.25)
51
+
52
+ PLATFORMS
53
+ ruby
54
+
55
+ DEPENDENCIES
56
+ bundler (~> 2.1)
57
+ commonmarker (~> 0.21)
58
+ github-markup (~> 3.0)
59
+ minitest (~> 5.14)
60
+ nvd_feed_api!
61
+ rake (~> 13.0)
62
+ redcarpet (~> 3.5)
63
+ rubocop (~> 0.92)
64
+ yard (~> 0.9)
65
+
66
+ BUNDLED WITH
67
+ 2.1.4
data/README.md CHANGED
@@ -5,6 +5,7 @@
5
5
  [![Gem stable](https://img.shields.io/gem/dv/nvd_feed_api/stable.svg)][rubygems]
6
6
  [![Gem latest](https://img.shields.io/gem/dtv/nvd_feed_api.svg)][rubygems]
7
7
  [![Gem total download](https://img.shields.io/gem/dt/nvd_feed_api.svg)][rubygems]
8
+ [![Rawsec's CyberSecurity Inventory](https://inventory.rawsec.ml/img/badges/Rawsec-inventoried-FF5050_flat.svg)](https://inventory.rawsec.ml/tools.html#nvd_feed_api)
8
9
 
9
10
  [rubygems]:https://rubygems.org/gems/nvd_feed_api/
10
11
 
@@ -12,7 +13,7 @@
12
13
 
13
14
  **nvd_feed_api** is a simple ruby API for NVD CVE feeds.
14
15
 
15
- The API will help you to download and manage NVD Data Feeds, search for CVEs, build your vulerability assesment platform or vulnerability database.
16
+ The API will help you to download and manage NVD Data Feeds, search for CVEs, build your vulnerability assessment platform or vulnerability database.
16
17
 
17
18
  Name | Link
18
19
  --- | ---
@@ -1,15 +1,13 @@
1
1
  # @author Alexandre ZANNI <alexandre.zanni@engineer.com>
2
2
 
3
3
  # Ruby internal
4
- require 'digest'
5
4
  require 'net/https'
6
5
  require 'set'
7
6
  # External
8
- require 'archive/zip'
9
7
  require 'nokogiri'
10
- require 'oj'
11
8
  # Project internal
12
9
  require 'nvd_feed_api/version'
10
+ require 'nvd_feed_api/feed'
13
11
 
14
12
  # The class that parse NVD website to get information.
15
13
  # @example Initialize a NVDFeedScraper object, get the feeds and see them:
@@ -20,332 +18,12 @@ require 'nvd_feed_api/version'
20
18
  # scraper.feeds("CVE-2007")
21
19
  # cve2007, cve2015 = scraper.feeds("CVE-2007", "CVE-2015")
22
20
  class NVDFeedScraper
21
+ BASE = 'https://nvd.nist.gov'.freeze
23
22
  # The NVD url where is located the data feeds.
24
- URL = 'https://nvd.nist.gov/vuln/data-feeds'.freeze
23
+ URL = "#{BASE}/vuln/data-feeds".freeze
25
24
  # Load constants
26
25
  include NvdFeedApi
27
26
 
28
- # Feed object.
29
- class Feed
30
- class << self
31
- # Get / set default feed storage location, where will be stored JSON feeds and archives by default.
32
- # @return [String] default feed storage location. Default to +/tmp/+.
33
- # @example
34
- # NVDFeedScraper::Feed.default_storage_location = '/srv/downloads/'
35
- attr_accessor :default_storage_location
36
- end
37
- @default_storage_location = '/tmp/'
38
-
39
- # @return [String] the name of the feed.
40
- # @example
41
- # 'CVE-2007'
42
- attr_reader :name
43
-
44
- # @return [String] the last update date of the feed information on the NVD website.
45
- # @example
46
- # '10/19/2017 3:27:02 AM -04:00'
47
- attr_reader :updated
48
-
49
- # @return [String] the URL of the metadata file of the feed.
50
- # @example
51
- # 'https://static.nvd.nist.gov/feeds/json/cve/1.0/nvdcve-1.0-2007.meta'
52
- attr_reader :meta_url
53
-
54
- # @return [String] the URL of the gz archive of the feed.
55
- # @example
56
- # 'https://static.nvd.nist.gov/feeds/json/cve/1.0/nvdcve-1.0-2007.json.gz'
57
- attr_reader :gz_url
58
-
59
- # @return [String] the URL of the zip archive of the feed.
60
- # @example
61
- # 'https://static.nvd.nist.gov/feeds/json/cve/1.0/nvdcve-1.0-2007.json.zip'
62
- attr_reader :zip_url
63
-
64
- # @return [Meta] the {Meta} object of the feed.
65
- # @note
66
- # Return nil if not previously loaded by {#meta_pull}.
67
- # Note that {#json_pull} also calls {#meta_pull}.
68
- # @example
69
- # s = NVDFeedScraper.new
70
- # s.scrap
71
- # f = s.feeds("CVE-2014")
72
- # f.meta # => nil
73
- # f.meta_pull
74
- # f.meta # => #<NVDFeedScraper::Meta:0x00555b53027570 ... >
75
- attr_reader :meta
76
-
77
- # @return [String] the path of the saved JSON file.
78
- # @note Return nil if not previously loaded by {#json_pull}.
79
- # @example
80
- # s = NVDFeedScraper.new
81
- # s.scrap
82
- # f = s.feeds("CVE-2014")
83
- # f.json_file # => nil
84
- # f.json_pull
85
- # f.json_file # => "/tmp/nvdcve-1.0-2014.json"
86
- attr_reader :json_file
87
-
88
- # A new instance of Feed.
89
- # @param name [String] see {#name}.
90
- # @param updated [String] see {#updated}.
91
- # @param meta_url [String] see {#meta_url}.
92
- # @param gz_url [String] see {#gz_url}.
93
- # @param zip_url [String] see {#zip_url}.
94
- def initialize(name, updated, meta_url, gz_url, zip_url)
95
- @name = name
96
- @updated = updated
97
- @meta_url = meta_url
98
- @gz_url = gz_url
99
- @zip_url = zip_url
100
- # do not pull meta and json automatically for speed and memory footprint
101
- @meta = nil
102
- @json_file = nil
103
- end
104
-
105
- # Create or update the {Meta} object (fill the attribute).
106
- # @return [Meta] the updated {Meta} object of the feed.
107
- # @see #meta
108
- def meta_pull
109
- meta_content = NVDFeedScraper::Meta.new(@meta_url)
110
- meta_content.parse
111
- # update @meta
112
- @meta = meta_content
113
- end
114
-
115
- # Download the gz archive of the feed.
116
- # @param opts [Hash] see {#download_file}.
117
- # @return [String] the saved gz file path.
118
- # @example
119
- # afeed.download_gz
120
- # afeed.download_gz(destination_path: '/srv/save/')
121
- def download_gz(opts = {})
122
- download_file(@gz_url, opts)
123
- end
124
-
125
- # Download the zip archive of the feed.
126
- # @param opts [Hash] see {#download_file}.
127
- # @return [String] the saved zip file path.
128
- # @example
129
- # afeed.download_zip
130
- # afeed.download_zip(destination_path: '/srv/save/')
131
- def download_zip(opts = {})
132
- download_file(@zip_url, opts)
133
- end
134
-
135
- # Download the JSON feed and fill the attribute.
136
- # @param opts [Hash] see {#download_file}.
137
- # @return [String] the path of the saved JSON file. Default use {Feed#default_storage_location}.
138
- # @note Will downlaod and save the zip of the JSON file, unzip and save it. This massively consume time.
139
- # @see #json_file
140
- def json_pull(opts = {})
141
- opts[:destination_path] ||= Feed.default_storage_location
142
-
143
- skip_download = false
144
- destination_path = opts[:destination_path]
145
- destination_path += '/' unless destination_path[-1] == '/'
146
- filename = URI(@zip_url).path.split('/').last.chomp('.zip')
147
- # do not use @json_file for destination_file because of offline loading
148
- destination_file = destination_path + filename
149
- meta_pull
150
- if File.file?(destination_file)
151
- # Verify hash to see if it is the latest
152
- computed_h = Digest::SHA256.file(destination_file)
153
- skip_download = true if meta.sha256.casecmp(computed_h.hexdigest).zero?
154
- end
155
- if skip_download
156
- @json_file = destination_file
157
- else
158
- zip_path = download_zip(opts)
159
- Archive::Zip.open(zip_path) do |z|
160
- z.extract(destination_path, flatten: true)
161
- end
162
- @json_file = zip_path.chomp('.zip')
163
- # Verify hash integrity
164
- computed_h = Digest::SHA256.file(@json_file)
165
- raise "File corruption: #{@json_file}" unless meta.sha256.casecmp(computed_h.hexdigest).zero?
166
- end
167
- return @json_file
168
- end
169
-
170
- # Search for CVE in the feed.
171
- # @overload cve(cve)
172
- # One CVE.
173
- # @param cve [String] CVE ID, case insensitive.
174
- # @return [Hash] a Ruby Hash corresponding to the CVE.
175
- # @overload cve(cve_arr)
176
- # An array of CVEs.
177
- # @param cve_arr [Array<String>] Array of CVE ID, case insensitive.
178
- # @return [Array] an Array of CVE, each CVE is a Ruby Hash. May not be in the same order as provided.
179
- # @overload cve(cve, *)
180
- # Multiple CVEs.
181
- # @param cve [String] CVE ID, case insensitive.
182
- # @param * [String] As many CVE ID as you want.
183
- # @return [Array] an Array of CVE, each CVE is a Ruby Hash. May not be in the same order as provided.
184
- # @note {#json_pull} is needed before using this method. Remember you're searching only in the current feed.
185
- # @todo implement a CVE Class instead of returning a Hash.
186
- # @see https://scap.nist.gov/schema/nvd/feed/0.1/nvd_cve_feed_json_0.1_beta.schema
187
- # @see https://scap.nist.gov/schema/nvd/feed/0.1/CVE_JSON_4.0_min.schema
188
- # @example
189
- # s = NVDFeedScraper.new
190
- # s.scrap
191
- # f = s.feeds("CVE-2014")
192
- # f.json_pull
193
- # f.cve("CVE-2014-0002", "cve-2014-0001")
194
- def cve(*arg_cve)
195
- raise 'json_file is nil, it needs to be populated with json_pull' if @json_file.nil?
196
- raise "json_file (#{@json_file}) doesn't exist" unless File.file?(@json_file)
197
- return_value = nil
198
- raise 'no argument provided, 1 or more expected' if arg_cve.empty?
199
- if arg_cve.length == 1
200
- if arg_cve[0].is_a?(String)
201
- raise "bad CVE name (#{arg_cve[0]})" unless /^CVE-[0-9]{4}-[0-9]{4,}$/i.match?(arg_cve[0])
202
- doc = Oj::Doc.open(File.read(@json_file))
203
- # Quicker than doc.fetch('/CVE_Items').size
204
- doc_size = doc.fetch('/CVE_data_numberOfCVEs').to_i
205
- (1..doc_size).each do |i|
206
- if arg_cve[0].upcase == doc.fetch("/CVE_Items/#{i}/cve/CVE_data_meta/ID")
207
- return_value = doc.fetch("/CVE_Items/#{i}")
208
- break
209
- end
210
- end
211
- doc.close
212
- elsif arg_cve[0].is_a?(Array)
213
- return_value = []
214
- # Sorting CVE can allow us to parse quicker
215
- # Upcase to be sure include? works
216
- cves_to_find = arg_cve[0].map(&:upcase).sort
217
- raise 'one of the provided arguments is not a String' unless cves_to_find.all? { |x| x.is_a?(String) }
218
- raise 'bad CVE name' unless cves_to_find.all? { |x| /^CVE-[0-9]{4}-[0-9]{4,}$/i.match?(x) }
219
- doc = Oj::Doc.open(File.read(@json_file))
220
- # Quicker than doc.fetch('/CVE_Items').size
221
- doc_size = doc.fetch('/CVE_data_numberOfCVEs').to_i
222
- (1..doc_size).each do |i|
223
- doc.move("/CVE_Items/#{i}")
224
- cve_id = doc.fetch('cve/CVE_data_meta/ID')
225
- if cves_to_find.include?(cve_id)
226
- return_value.push(doc.fetch)
227
- cves_to_find.delete(cve_id)
228
- elsif cves_to_find.empty?
229
- break
230
- end
231
- end
232
- raise "#{cves_to_find.join(', ')} are unexisting CVEs in this feed" unless cves_to_find.empty?
233
- else
234
- raise "the provided argument (#{arg_cve[0]}) is nor a String or an Array"
235
- end
236
- else
237
- # Overloading a list of arguments as one array argument
238
- return_value = cve(arg_cve)
239
- end
240
- return return_value
241
- end
242
-
243
- # Return a list with the name of all available CVEs in the feed.
244
- # Can only be called after {#json_pull}.
245
- # @return [Array<String>] List with the name of all available CVEs. May return thousands CVEs.
246
- def available_cves
247
- raise 'json_file is nil, it needs to be populated with json_pull' if @json_file.nil?
248
- raise "json_file (#{@json_file}) doesn't exist" unless File.file?(@json_file)
249
- doc = Oj::Doc.open(File.read(@json_file))
250
- # Quicker than doc.fetch('/CVE_Items').size
251
- doc_size = doc.fetch('/CVE_data_numberOfCVEs').to_i
252
- cve_names = []
253
- (1..doc_size).each do |i|
254
- doc.move("/CVE_Items/#{i}")
255
- cve_names.push(doc.fetch('cve/CVE_data_meta/ID'))
256
- end
257
- doc.close
258
- return cve_names
259
- end
260
-
261
- protected
262
-
263
- # @param arg_name [String] the new name of the feed.
264
- # @return [String] the new name of the feed.
265
- # @example
266
- # 'CVE-2007'
267
- def name=(arg_name)
268
- raise "name (#{arg_name}) is not a string" unless arg_name.is_a(String)
269
- @name = arg_name
270
- end
271
-
272
- # @param arg_updated [String] the last update date of the feed information on the NVD website.
273
- # @return [String] the new date.
274
- # @example
275
- # '10/19/2017 3:27:02 AM -04:00'
276
- def updated=(arg_updated)
277
- raise "updated date (#{arg_updated}) is not a string" unless arg_updated.is_a(String)
278
- @updated = arg_updated
279
- end
280
-
281
- # @param arg_meta_url [String] the new URL of the metadata file of the feed.
282
- # @return [String] the new URL of the metadata file of the feed.
283
- # @example
284
- # 'https://static.nvd.nist.gov/feeds/json/cve/1.0/nvdcve-1.0-2007.meta'
285
- def meta_url=(arg_meta_url)
286
- raise "meta_url (#{arg_meta_url}) is not a string" unless arg_meta_url.is_a(String)
287
- @meta_url = arg_meta_url
288
- end
289
-
290
- # @param arg_gz_url [String] the new URL of the gz archive of the feed.
291
- # @return [String] the new URL of the gz archive of the feed.
292
- # @example
293
- # 'https://static.nvd.nist.gov/feeds/json/cve/1.0/nvdcve-1.0-2007.json.gz'
294
- def gz_url=(arg_gz_url)
295
- raise "gz_url (#{arg_gz_url}) is not a string" unless arg_gz_url.is_a(String)
296
- @gz_url = arg_gz_url
297
- end
298
-
299
- # @param arg_zip_url [String] the new URL of the zip archive of the feed.
300
- # @return [String] the new URL of the zip archive of the feed.
301
- # @example
302
- # 'https://static.nvd.nist.gov/feeds/json/cve/1.0/nvdcve-1.0-2007.json.zip'
303
- def zip_url=(arg_zip_url)
304
- raise "zip_url (#{arg_zip_url}) is not a string" unless arg_zip_url.is_a(String)
305
- @zip_url = arg_zip_url
306
- end
307
-
308
- # Download a file.
309
- # @param file_url [String] the URL of the file.
310
- # @param opts [Hash] the optional downlaod parameters.
311
- # @option opts [String] :destination_path the destination path (may
312
- # overwrite existing file).
313
- # Default use {Feed#default_storage_location}.
314
- # @option opts [String] :sha256 the SHA256 hash to check, if the file
315
- # already exist and the hash matches then the download will be skipped.
316
- # @return [String] the saved file path.
317
- # @example
318
- # download_file('https://example.org/example.zip') # => '/tmp/example.zip'
319
- # download_file('https://example.org/example.zip', destination_path: '/srv/save/') # => '/srv/save/example.zip'
320
- # download_file('https://example.org/example.zip', {destination_path: '/srv/save/', sha256: '70d6ea136d5036b6ce771921a949357216866c6442f44cea8497f0528c54642d'}) # => '/srv/save/example.zip'
321
- def download_file(file_url, opts = {})
322
- opts[:destination_path] ||= Feed.default_storage_location
323
- opts[:sha256] ||= nil
324
-
325
- destination_path = opts[:destination_path]
326
- destination_path += '/' unless destination_path[-1] == '/'
327
- skip_download = false
328
- uri = URI(file_url)
329
- filename = uri.path.split('/').last
330
- destination_file = destination_path + filename
331
- unless opts[:sha256].nil?
332
- if File.file?(destination_file)
333
- # Verify hash to see if it is the latest
334
- computed_h = Digest::SHA256.file(destination_file)
335
- skip_download = true if opts[:sha256].casecmp(computed_h.hexdigest).zero?
336
- end
337
- end
338
- unless skip_download
339
- res = Net::HTTP.get_response(uri)
340
- raise "#{file_url} ended with #{res.code} #{res.message}" unless res.is_a?(Net::HTTPSuccess)
341
- open(destination_file, 'wb') do |file|
342
- file.write(res.body)
343
- end
344
- end
345
- return destination_file
346
- end
347
- end
348
-
349
27
  # Initialize the scraper
350
28
  def initialize
351
29
  @url = URL
@@ -354,21 +32,33 @@ class NVDFeedScraper
354
32
 
355
33
  # Scrap / parse the website to get the feeds and fill the {#feeds} attribute.
356
34
  # @note {#scrap} need to be called only once but can be called again to update if the NVD feed page changed.
357
- # @return [Integer] +0+ when there is no error.
35
+ # @return [Integer] Number of scrapped feeds.
358
36
  def scrap
359
37
  uri = URI(@url)
360
38
  html = Net::HTTP.get(uri)
361
39
 
362
40
  doc = Nokogiri::HTML(html)
363
41
  @feeds = []
364
- doc.css('h3#JSON_FEED ~ div.row:first-of-type table.xml-feed-table > tbody > tr[data-testid*=desc]').each do |tr|
365
- name = tr.css('td')[0].text
366
- updated = tr.css('td')[1].text
367
- meta = tr.css('td')[2].css('> a').attr('href').value
368
- gz = tr.css('+ tr > td > a').attr('href').value
369
- zip = tr.css('+ tr + tr > td > a').attr('href').value
370
- @feeds.push(Feed.new(name, updated, meta, gz, zip))
42
+ tmp_feeds = {}
43
+ doc.css('#vuln-feed-table table.xml-feed-table tr[data-testid]').each do |tr|
44
+ num, type = tr.attr('data-testid')[13..].split('-')
45
+ if type == 'meta'
46
+ tmp_feeds[num] = {}
47
+ tmp_feeds[num][:name] = tr.css('td')[0].text
48
+ tmp_feeds[num][:updated] = tr.css('td')[1].text
49
+ tmp_feeds[num][:meta] = BASE + tr.css('td')[2].css('> a').attr('href').value
50
+ elsif type == 'gz'
51
+ tmp_feeds[num][:gz] = BASE + tr.css('td > a').attr('href').value
52
+ elsif type == 'zip'
53
+ tmp_feeds[num][:zip] = BASE + tr.css('td > a').attr('href').value
54
+ @feeds.push(Feed.new(tmp_feeds[num][:name],
55
+ tmp_feeds[num][:updated],
56
+ tmp_feeds[num][:meta],
57
+ tmp_feeds[num][:gz],
58
+ tmp_feeds[num][:zip]))
59
+ end
371
60
  end
61
+ return @feeds.size
372
62
  end
373
63
 
374
64
  # Return feeds. Can only be called after {#scrap}.
@@ -395,6 +85,7 @@ class NVDFeedScraper
395
85
  # @see https://nvd.nist.gov/vuln/data-feeds
396
86
  def feeds(*arg_feeds)
397
87
  raise 'call scrap method before using feeds method' if @feeds.nil?
88
+
398
89
  return_value = nil
399
90
  if arg_feeds.empty?
400
91
  return_value = @feeds
@@ -406,6 +97,7 @@ class NVDFeedScraper
406
97
  # if nothing found return nil
407
98
  elsif arg_feeds[0].is_a?(Array)
408
99
  raise 'one of the provided arguments is not a String' unless arg_feeds[0].all? { |x| x.is_a?(String) }
100
+
409
101
  # Sorting CVE can allow us to parse quicker
410
102
  # Upcase to be sure include? works
411
103
  # Does not use map(&:upcase) to preserve CVE-Recent and CVE-Modified
@@ -437,6 +129,7 @@ class NVDFeedScraper
437
129
  # scraper.available_feeds => ["CVE-Modified", "CVE-Recent", "CVE-2017", "CVE-2016", "CVE-2015", "CVE-2014", "CVE-2013", "CVE-2012", "CVE-2011", "CVE-2010", "CVE-2009", "CVE-2008", "CVE-2007", "CVE-2006", "CVE-2005", "CVE-2004", "CVE-2003", "CVE-2002"]
438
130
  def available_feeds
439
131
  raise 'call scrap method before using available_feeds method' if @feeds.nil?
132
+
440
133
  feed_names = []
441
134
  @feeds.each do |feed| # feed is an objet
442
135
  feed_names.push(feed.name)
@@ -469,9 +162,11 @@ class NVDFeedScraper
469
162
  def cve(*arg_cve)
470
163
  return_value = nil
471
164
  raise 'no argument provided, 1 or more expected' if arg_cve.empty?
165
+
472
166
  if arg_cve.length == 1
473
167
  if arg_cve[0].is_a?(String)
474
168
  raise 'bad CVE name' unless /^CVE-[0-9]{4}-[0-9]{4,}$/i.match?(arg_cve[0])
169
+
475
170
  year = /^CVE-([0-9]{4})-[0-9]{4,}$/i.match(arg_cve[0]).captures[0]
476
171
  matched_feed = nil
477
172
  feed_names = available_feeds
@@ -483,13 +178,17 @@ class NVDFeedScraper
483
178
  break
484
179
  end
485
180
  end
181
+ # CVE-2002 feed (the 1st one) contains CVE from 1999 to 2002
182
+ matched_feed = 'CVE-2002' if matched_feed.nil? && ('1999'..'2001').to_a.include?(year)
486
183
  raise "bad CVE year in #{arg_cve}" if matched_feed.nil?
184
+
487
185
  f = feeds(matched_feed)
488
186
  f.json_pull
489
187
  return_value = f.cve(arg_cve[0])
490
188
  elsif arg_cve[0].is_a?(Array)
491
189
  raise 'one of the provided arguments is not a String' unless arg_cve[0].all? { |x| x.is_a?(String) }
492
190
  raise 'bad CVE name' unless arg_cve[0].all? { |x| /^CVE-[0-9]{4}-[0-9]{4,}$/i.match?(x) }
191
+
493
192
  return_value = []
494
193
  # Sorting CVE can allow us to parse quicker
495
194
  # Upcase to be sure include? works
@@ -501,8 +200,18 @@ class NVDFeedScraper
501
200
  feed_names = available_feeds.to_set
502
201
  feed_names.delete('CVE-Modified')
503
202
  feed_names.delete('CVE-Recent')
203
+ # CVE-2002 feed (the 1st one) contains CVE from 1999 to 2002
204
+ virtual_feeds = ['CVE-1999', 'CVE-2000', 'CVE-2001']
205
+ # So virtually add those feed...
206
+ feed_names.merge(virtual_feeds)
504
207
  raise 'unexisting CVE year was provided in some CVE' unless feeds_to_match.subset?(feed_names)
208
+
505
209
  matched_feeds = feeds_to_match.intersection(feed_names)
210
+ # and now that the intersection is done remove those virtual feeds and add CVE-2002 instead if needed
211
+ unless matched_feeds.intersection(virtual_feeds.to_set).empty?
212
+ matched_feeds.subtract(virtual_feeds)
213
+ matched_feeds.add('CVE-2002')
214
+ end
506
215
  feeds_arr = feeds(matched_feeds.to_a)
507
216
  feeds_arr.each do |feed|
508
217
  feed.json_pull
@@ -547,23 +256,13 @@ class NVDFeedScraper
547
256
  def update_feeds(*arg_feed)
548
257
  return_value = false
549
258
  raise 'no argument provided, 1 or more expected' if arg_feed.empty?
259
+
550
260
  scrap
551
261
  if arg_feed.length == 1
552
262
  if arg_feed[0].is_a?(Feed)
553
263
  new_feed = feeds(arg_feed[0].name)
554
264
  # update attributes
555
- if arg_feed[0].updated != new_feed.updated
556
- arg_feed[0].name = new_feed.name
557
- arg_feed[0].updated = new_feed.updated
558
- arg_feed[0].meta_url = new_feed.meta_url
559
- arg_feed[0].gz_url = new_feed.gz_url
560
- arg_feed[0].zip_url = new_feed.zip_url
561
- # update if @meta was set
562
- arg_feed[0].meta_pull unless feed.meta.nil?
563
- # update if @json_file was set
564
- arg_feed[0].json_pull unless feed.json_file.nil?
565
- return_value = true
566
- end
265
+ return_value = arg_feed[0].update!(new_feed)
567
266
  elsif arg_feed[0].is_a?(Array)
568
267
  return_value = []
569
268
  arg_feed[0].each do |f|
@@ -597,115 +296,4 @@ class NVDFeedScraper
597
296
  end
598
297
  return cve_names
599
298
  end
600
-
601
- # Manage the meta file from a feed.
602
- #
603
- # == Usage
604
- #
605
- # @example
606
- # s = NVDFeedScraper.new
607
- # s.scrap
608
- # metaUrl = s.feeds("CVE-2014").meta_url
609
- # m = NVDFeedScraper::Meta.new
610
- # m.url = metaUrl
611
- # m.parse
612
- # m.sha256
613
- #
614
- # Several ways to set the url:
615
- #
616
- # m = NVDFeedScraper::Meta.new(metaUrl)
617
- # m.parse
618
- # # or
619
- # m = NVDFeedScraper::Meta.new
620
- # m.url = metaUrl
621
- # m.parse
622
- # # or
623
- # m = NVDFeedScraper::Meta.new
624
- # m.parse(metaUrl)
625
- class Meta
626
- # {Meta} last modified date getter
627
- # @return [String] the last modified date and time.
628
- # @example
629
- # '2017-10-19T03:27:02-04:00'
630
- attr_reader :last_modified_date
631
-
632
- # {Meta} JSON size getter
633
- # @return [String] the size of the JSON file uncompressed.
634
- # @example
635
- # '29443314'
636
- attr_reader :size
637
-
638
- # {Meta} zip size getter
639
- # @return [String] the size of the zip file.
640
- # @example
641
- # '2008493'
642
- attr_reader :zip_size
643
-
644
- # {Meta} gz size getter
645
- # @return [String] the size of the gz file.
646
- # @example
647
- # '2008357'
648
- attr_reader :gz_size
649
-
650
- # {Meta} JSON sha256 getter
651
- # @return [String] the SHA256 value of the uncompressed JSON file.
652
- # @example
653
- # '33ED52D451692596D644F23742ED42B4E350258B11ACB900F969F148FCE3777B'
654
- attr_reader :sha256
655
-
656
- # @param url [String, nil] see {Feed#meta_url}.
657
- def initialize(url = nil)
658
- @url = url
659
- end
660
-
661
- # {Meta} URL getter.
662
- # @return [String] The URL of the meta file of the feed.
663
- attr_reader :url
664
-
665
- # {Meta} URL setter.
666
- # @param url [String] see {Feed#meta_url}.
667
- def url=(url)
668
- @url = url
669
- @last_modified_date = @size = @zip_size = @gz_size = @sha256 = nil
670
- end
671
-
672
- # Parse the meta file from the URL and set the attributes.
673
- # @overload parse
674
- # Parse the meta file from the URL and set the attributes.
675
- # @return [Integer] Returns +0+ when there is no error.
676
- # @overload parse(url)
677
- # Set the URL of the meta file of the feed and
678
- # parse the meta file from the URL and set the attributes.
679
- # @param url [String] see {Feed.meta_url}
680
- # @return [Integer] Returns +0+ when there is no error.
681
- def parse(*arg)
682
- if arg.empty?
683
- elsif arg.length == 1 # arg = url
684
- self.url = arg[0]
685
- else
686
- raise 'Too much arguments'
687
- end
688
-
689
- raise "Can't parse if the URL is empty" if @url.nil?
690
- uri = URI(@url)
691
-
692
- meta = Net::HTTP.get(uri)
693
-
694
- meta = Hash[meta.split.map { |x| x.split(':', 2) }]
695
-
696
- raise 'no lastModifiedDate attribute found' unless meta['lastModifiedDate']
697
- raise 'no valid size attribute found' unless /[0-9]+/.match?(meta['size'])
698
- raise 'no valid zipSize attribute found' unless /[0-9]+/.match?(meta['zipSize'])
699
- raise 'no valid gzSize attribute found' unless /[0-9]+/.match?(meta['gzSize'])
700
- raise 'no valid sha256 attribute found' unless /[0-9A-F]{64}/.match?(meta['sha256'])
701
-
702
- @last_modified_date = meta['lastModifiedDate']
703
- @size = meta['size']
704
- @zip_size = meta['zipSize']
705
- @gz_size = meta['gzSize']
706
- @sha256 = meta['sha256']
707
-
708
- 0
709
- end
710
- end
711
299
  end