160 lines
4.2 KiB
Ruby
160 lines
4.2 KiB
Ruby
#!/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
|