From a6c30074cd97066f060aa5903b5df30d8665db58 Mon Sep 17 00:00:00 2001 From: david942j Date: Sun, 12 Feb 2017 00:32:48 +0800 Subject: [PATCH] support fetch builds from repo --- lib/one_gadget/gadget.rb | 11 ++++--- lib/one_gadget/helper.rb | 62 ++++++++++++++++++++++++++++++++++++++++ spec/gadget_spec.rb | 4 +++ spec/one_gadget_spec.rb | 12 ++++++-- 4 files changed, 82 insertions(+), 7 deletions(-) diff --git a/lib/one_gadget/gadget.rb b/lib/one_gadget/gadget.rb index 884177d..cfcfb25 100644 --- a/lib/one_gadget/gadget.rb +++ b/lib/one_gadget/gadget.rb @@ -45,11 +45,14 @@ module OneGadget require_all if BUILDS.empty? return BUILDS[build_id] if BUILDS.key?(build_id) # fetch remote builds - # table = OneGadget::Helper.remote_builds.find { |c| c.include?(build_id) } - # return [] if table.nil? # remote doesn't have this one. + table = OneGadget::Helper.remote_builds.find { |c| c.include?(build_id) } + return [] if table.nil? # remote doesn't have this one either. # builds found in remote! Ask update gem and download remote gadgets. - # TODO: fetch remote builds information. - [] + OneGadget::Helper.ask_update(msg: 'The desired one-gadget can be found in lastest version!') + tmp_file = OneGadget::Helper.download_build(table) + require tmp_file.path + tmp_file.unlink + BUILDS[build_id] end # Add a gadget, for scripts in builds/ to use. diff --git a/lib/one_gadget/helper.rb b/lib/one_gadget/helper.rb index d92c986..c75a364 100644 --- a/lib/one_gadget/helper.rb +++ b/lib/one_gadget/helper.rb @@ -1,5 +1,8 @@ require 'pathname' require 'shellwords' +require 'net/http' +require 'openssl' +require 'tempfile' module OneGadget # Define some helpful methods here. @@ -60,6 +63,65 @@ module OneGadget color = cc.key?(sev) ? cc[sev] : '' "#{color}#{str.sub(cc[:esc_m], color)}#{cc[:esc_m]}" end + + # Fetch the latest release version's tag name. + # @return [String] The tag name, in form +vx.x.x+. + def latest_tag + latest = url_request('https://github.com/david942j/one_gadget/releases').scan(%r{/tree/v([\d.]+)"}).map do |tag| + Gem::Version.new(tag.first) + end.max.to_s + 'v' + latest + end + + # Get the url which can fetch +filename+ from remote repo. + # @param [String] filename + # @return [String] The url. + def url_of_file(filename) + raw_file_url = 'https://raw.githubusercontent.com/david942j/one_gadget/@tag/@file' + raw_file_url.gsub('@tag', latest_tag).gsub('@file', filename) + end + + # Download the latest version of +file+ in +lib/one_gadget/builds/+ from remote repo. + # + # @param [String] file The filename desired. + # @return [Tempfile] The temp file be created. + def download_build(file) + temp = Tempfile.new(['gadgets', file + '.rb']) + url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb'))) + temp.write url_request(url_of_file(File.join('lib', 'one_gadget', 'builds', file + '.rb'))) + temp.close + temp + end + + # Get the latest builds list from repo. + # @return [Array] List of build ids. + def remote_builds + url_request(url_of_file('builds_list')).lines.map(&:strip) + end + + # Get request. + # @param [String] url The url. + # @return [String] The request response body. + def url_request(url) + # TODO: add timeout to handle github crashed or in no network environment. + uri = URI.parse(url) + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + http.verify_mode = ::OpenSSL::SSL::VERIFY_NONE + + request = Net::HTTP::Get.new(uri.request_uri) + + response = http.request(request) + response.body + end + + # Show the message of ask user to update gem. + # @return [void] + def ask_update(msg: '') + name = 'one_gadget' + cmd = colorize("gem update #{name}") + STDERR.puts msg + "\n" + "Update with: $ #{cmd}" + end end extend ClassMethods end diff --git a/spec/gadget_spec.rb b/spec/gadget_spec.rb index ed151fc..118d728 100644 --- a/spec/gadget_spec.rb +++ b/spec/gadget_spec.rb @@ -7,6 +7,10 @@ describe OneGadget::Gadget do OneGadget::Gadget.add(@build_id, 0x1234, constraints: ['[rsp+0x30] == NULL', 'rax == 0']) end + after(:all) do + OneGadget::Gadget::ClassMethods::BUILDS.delete @build_id + end + it 'inspect' do expect(OneGadget::Gadget.builds(@build_id).map(&:inspect).join).to eq <<-EOS offset: 0x1234 diff --git a/spec/one_gadget_spec.rb b/spec/one_gadget_spec.rb index 8497bf1..5a52e70 100644 --- a/spec/one_gadget_spec.rb +++ b/spec/one_gadget_spec.rb @@ -1,9 +1,7 @@ require 'one_gadget' describe 'one_gadget' do - before(:all) do - # To force require all again. - OneGadget::Gadget::ClassMethods::BUILDS.clear + before(:each) do @build_id = '60131540dadc6796cab33388349e6e4e68692053' @libcpath = File.join(File.dirname(__FILE__), 'data', 'libc-2.19.so') end @@ -21,5 +19,13 @@ describe 'one_gadget' do it 'invalid' do expect { OneGadget.gadgets(build_id: '^_^') }.to raise_error(ArgumentError, 'invalid BuildID format: "^_^"') end + + it 'fetch from remote' do + entry = OneGadget::Gadget::ClassMethods::BUILDS.delete(@build_id) + OneGadget::Gadget::ClassMethods::BUILDS[:a] = 1 + expect(OneGadget.gadgets(build_id: @build_id)).not_to be_empty + OneGadget::Gadget::ClassMethods::BUILDS.delete(:a) + OneGadget::Gadget::ClassMethods::BUILDS[@build_id] = entry unless entry.nil? + end end end