Land #6667, add a dev script for finding Metasploit release notes for modules
commit
558f810165
|
@ -0,0 +1,159 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
require 'net/http'
|
||||
require 'nokogiri'
|
||||
require 'thread'
|
||||
|
||||
module ReleaseNotesFinder
|
||||
# This finds the release notes information based on either:
|
||||
# 1. A PR number. In release notes, PR numbers are for bug fixes and notable changes.
|
||||
# 2. A module short name. For example: ms08_067_netapi
|
||||
class Client
|
||||
attr_accessor :release_notes
|
||||
|
||||
RELEASE_NOTES_PAGE = 'https://community.rapid7.com/docs/DOC-2918'.freeze
|
||||
|
||||
def initialize
|
||||
init_release_notes
|
||||
@mutex = Mutex.new
|
||||
end
|
||||
|
||||
def add_release_notes_entry(row)
|
||||
td = row.search('td')
|
||||
release_notes_link = td[0] && td[0].at('a') ? td[0].at('a').attributes['href'].value : ''
|
||||
release_notes_num = td[0] && td[0].at('a') ? td[0].at('a').text.scan(/\d{10}/).flatten.first || '' : ''
|
||||
highlights = td[1] ? (td[1].search('span') || []).map { |e| e.text } * " " : ''
|
||||
update_link = td[2] && td[2].at('a') ? td[2].at('a').attributes['href'].value : ''
|
||||
|
||||
@release_notes << {
|
||||
release_notes_link: release_notes_link,
|
||||
release_notes_num: release_notes_num,
|
||||
highlights: highlights,
|
||||
update_link: update_link,
|
||||
pull_requests: [],
|
||||
new_modules: []
|
||||
}
|
||||
end
|
||||
|
||||
def init_release_notes
|
||||
self.release_notes = []
|
||||
|
||||
html = send_http_request(RELEASE_NOTES_PAGE)
|
||||
table_rows_pattern = 'div[@id="jive-body-main"]//div//section//div//div[@class="j-rte-table"]//table//tbody//tr'
|
||||
rows = html.search(table_rows_pattern)
|
||||
rows.each do |row|
|
||||
add_release_notes_entry(row)
|
||||
end
|
||||
end
|
||||
|
||||
def update_pr_list(n, text)
|
||||
pr_num, desc = text.scan(/#(\d+).\x20*(.+)/).flatten
|
||||
return unless pr_num
|
||||
n[:pull_requests] << { id: pr_num, description: desc }
|
||||
end
|
||||
|
||||
def update_module_list(n, li)
|
||||
li.search('a').each do |a|
|
||||
next if a.attributes['href'].nil?
|
||||
n[:new_modules] << { link: a.attributes['href'].value }
|
||||
end
|
||||
end
|
||||
|
||||
def update_release_notes_entry(n)
|
||||
html = send_http_request(n[:release_notes_link])
|
||||
pattern = '//div[@class="jive-rendered-content"]//ul//li'
|
||||
html.search(pattern).each do |li|
|
||||
@mutex.synchronize do
|
||||
update_pr_list(n, li.text)
|
||||
update_module_list(n, li)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def get_release_notes(input)
|
||||
release_notes.each do |n|
|
||||
if n[:pull_requests].empty?
|
||||
update_release_notes_entry(n)
|
||||
end
|
||||
|
||||
input_type = guess_input_type(input)
|
||||
|
||||
case input_type
|
||||
when :pr
|
||||
m = get_release_notes_from_pr(n, input)
|
||||
when :module_name
|
||||
m = get_release_notes_from_module_name(n, input)
|
||||
end
|
||||
|
||||
return m if m
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def guess_input_type(input)
|
||||
input =~ /^\d+/ ? :pr : :module_name
|
||||
end
|
||||
|
||||
def get_release_notes_from_module_name(n, input)
|
||||
n[:new_modules].each do |m|
|
||||
return n if m[:link] && m[:link].include?(input)
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def get_release_notes_from_pr(n, pr)
|
||||
n[:pull_requests].each do |p|
|
||||
return n if p[:id] && pr == p[:id]
|
||||
end
|
||||
|
||||
nil
|
||||
end
|
||||
|
||||
def send_http_request(uri)
|
||||
url = URI.parse(uri)
|
||||
cli = Net::HTTP.new(url.host, url.port)
|
||||
cli.use_ssl = true
|
||||
req = Net::HTTP::Get.new(url.request_uri)
|
||||
res = cli.request(req)
|
||||
Nokogiri::HTML(res.body)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def main
|
||||
inputs = []
|
||||
|
||||
ARGV.length.times { inputs << ARGV.shift }
|
||||
puts "[*] Enumerating release notes..."
|
||||
cli = ReleaseNotesFinder::Client.new
|
||||
puts "[*] Finding release notes for items: #{inputs * ', '}"
|
||||
threads = []
|
||||
begin
|
||||
inputs.each do |input|
|
||||
t = Thread.new do
|
||||
n = cli.get_release_notes(input)
|
||||
puts "\n"
|
||||
|
||||
if n
|
||||
puts "[*] Found release notes for: #{input}"
|
||||
puts "Release Notes Number: #{n[:release_notes_num]}"
|
||||
puts "Release Notes Link: #{n[:release_notes_link] || 'N/A'}"
|
||||
puts "Update Link: #{n[:update_link] || 'N/A'}"
|
||||
puts "Highlights:\n#{n[:highlights]}"
|
||||
else
|
||||
puts "[*] Unable to find release notes for: #{input}"
|
||||
end
|
||||
end
|
||||
threads << t
|
||||
end
|
||||
threads.each { |t| t.join }
|
||||
ensure
|
||||
threads.each { |t| t.kill }
|
||||
end
|
||||
end
|
||||
|
||||
if __FILE__ == $PROGRAM_NAME
|
||||
main
|
||||
end
|
Loading…
Reference in New Issue