├── test ├── http_get.txt ├── http_post.txt └── request_spec.rb ├── Rakefile ├── http2code.gemspec ├── lib └── http2code │ ├── templates │ └── ruby │ │ ├── curb │ │ ├── net_http │ │ └── typhoeus │ └── request.rb ├── README.md └── bin └── http2code /test/http_get.txt: -------------------------------------------------------------------------------- 1 | GET / HTTP/1.1 2 | Host: http://www.example.org 3 | User-Agent: Mozilla/5.0 (X11; Linux i686; rv:11.0) Gecko/20100101 Firefox/11.0 4 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 5 | Accept-Language: en-us,en;q=0.5 6 | Accept-Encoding: gzip, deflate 7 | Connection: keep-alive 8 | 9 | -------------------------------------------------------------------------------- /test/http_post.txt: -------------------------------------------------------------------------------- 1 | POST / HTTP/1.1 2 | Host: http://www.example.org 3 | User-Agent: Mozilla/5.0 (X11; Linux i686; rv:11.0) Gecko/20100101 Firefox/11.0 4 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8 5 | Accept-Language: en-us,en;q=0.5 6 | Accept-Encoding: gzip, deflate 7 | Connection: keep-alive 8 | 9 | param1=1¶m2=2¶m3=3¶m1=4 10 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require 'yard' 2 | require 'rake/clean' 3 | 4 | CLEAN.include('doc/', '*.gem') 5 | 6 | task :build do 7 | puts `gem build http2code.gemspec` 8 | end 9 | 10 | task :install => [:clean, :build] do 11 | puts `gem install http2code-*.gem` 12 | end 13 | 14 | YARD::Rake::YardocTask.new do |t| 15 | t.files = ['bin/http2code', 'lib/http2code/request.rb'] 16 | t.options = ['--main', 'README.markdown', '--markup', 'markdown'] 17 | end 18 | -------------------------------------------------------------------------------- /http2code.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | 3 | Gem::Specification.new do |s| 4 | s.name = %q{http2code} 5 | s.version = "0.0.3" 6 | s.authors = ["Patrick Hof"] 7 | s.date = %q{2012-04-30} 8 | s.email = %q{courts@offensivethinking.org} 9 | s.files = %w(bin/http2code lib/http2code/request.rb lib/http2code/templates/ruby/typhoeus lib/http2code/templates/ruby/curb lib/http2code/templates/ruby/net_http) 10 | s.executables = ["http2code"] 11 | s.homepage = %q{http://www.offensivethinking.org} 12 | s.require_paths = ["lib"] 13 | s.summary = %q{A script to turn a raw HTTP GET or POST request into template code snippets for different languages} 14 | end 15 | -------------------------------------------------------------------------------- /lib/http2code/templates/ruby/curb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #vim fileencoding=utf-8 3 | 4 | require 'curb' 5 | require 'json' 6 | require 'cgi' 7 | 8 | req = JSON.load(' 9 | %REQ% 10 | ') 11 | 12 | header = JSON.load(' 13 | %HEADER% 14 | ') 15 | 16 | body = CGI.escape(JSON.load(' 17 | %BODY% 18 | ').inject([]) do |m, (k,a)| 19 | a.each {|v| m << "#{k}=#{v}"}; m 20 | end.join("&")) 21 | 22 | c = Curl::Easy.new do |curl| 23 | # curl.ssl_verify_host = false 24 | curl.url = req["Url"] 25 | curl.headers = header 26 | if req["Verb"] == "POST" 27 | curl.post_body = body 28 | end 29 | end 30 | 31 | # Peform the request 32 | c.http(req["Verb"].upcase) 33 | 34 | # What to do when the request succeeds with a 20x response 35 | c.on_success do |c| 36 | return c.body_str 37 | end 38 | -------------------------------------------------------------------------------- /lib/http2code/templates/ruby/net_http: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'net/https' 4 | require 'json' 5 | require 'uri' 6 | require 'cgi' 7 | 8 | req = JSON.load(' 9 | %REQ% 10 | ') 11 | 12 | header = JSON.load(' 13 | %HEADER% 14 | ') 15 | 16 | body = CGI.escape(JSON.load(' 17 | %BODY% 18 | ').inject([]) do |m, (k,a)| 19 | a.each {|v| m << "#{k}=#{v}"}; m 20 | end.join("&")) 21 | 22 | # proxy_addr = "localhost" 23 | # proxy_port = 8118 24 | 25 | uri = URI.parse(req["Url"]) 26 | 27 | # http = Net::HTTP::Proxy(proxy_addr, proxy_port).new(url.host) 28 | http = Net::HTTP.new(uri.host) 29 | http.use_ssl = true if uri.scheme == "https" 30 | # http.verify_mode = OpenSSL::SSL::VERIFY_NONE 31 | res = http.start do |web| 32 | req = Net::HTTP::Options.new(uri.path) 33 | # req.basic_auth "username", "password" 34 | web.request(req) 35 | end 36 | 37 | puts "Response: (#{res.code})" 38 | print res.body 39 | -------------------------------------------------------------------------------- /test/request_spec.rb: -------------------------------------------------------------------------------- 1 | require 'bacon' 2 | require 'http2code/request.rb' 3 | 4 | describe Request do 5 | before do 6 | options = { 7 | :delimiter => "\r\n", 8 | :template => nil, 9 | :header_bl => false, 10 | :pretty => true 11 | } 12 | @req_get = Request.new(File.open('http_get.txt').read(), options) 13 | @req_post = Request.new(File.open('http_post.txt').read(), options) 14 | end 15 | 16 | it 'should parse headers' do 17 | @req_get.header.should == { 18 | "Host" => "http://www.example.org", 19 | "User-Agent" => "Mozilla/5.0 (X11; Linux i686; rv:11.0) Gecko/20100101 Firefox/11.0", 20 | "Accept" => "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 21 | "Accept-Language" => "en-us,en;q=0.5", 22 | "Accept-Encoding" => "gzip, deflate", 23 | "Connection" => "keep-alive" 24 | } 25 | 26 | @req_get.body.should == {} 27 | end 28 | 29 | it 'should parse a POST body' do 30 | @req_post.body.should == { 31 | "param1" => ["1", "4"], 32 | "param2" => ["2"], 33 | "param3" => ["3"] 34 | } 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/http2code/templates/ruby/typhoeus: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | #vim fileencoding=utf-8 3 | 4 | require 'typhoeus' 5 | require 'json' 6 | require 'cgi' 7 | 8 | req = JSON.load(' 9 | %REQ% 10 | ') 11 | 12 | header = JSON.load(' 13 | %HEADER% 14 | ') 15 | 16 | body = CGI.escape(JSON.load(' 17 | %BODY% 18 | ').inject([]) do |m, (k,a)| 19 | a.each {|v| m << "#{k}=#{v}"}; m 20 | end.join("&")) 21 | 22 | hydra = Typhoeus::Hydra.new 23 | hydra.disable_memoization 24 | 25 | request = Typhoeus::Request.new(req["Url"], 26 | :body => body, 27 | :method => req["Verb"].downcase.to_sym, 28 | :headers => header 29 | #:params => {:field1 => "a field"}) 30 | #:timeout => 100 # milliseconds 31 | #:cache_timeout => 60 # seconds 32 | #:follow_location => true 33 | #:max_redirects => 20 34 | #:proxy => "localhost:8080" 35 | #:disable_ssl_peer_verification => true 36 | ) 37 | 38 | request.on_complete do |response| 39 | # Handle response: 40 | # 41 | # response.code # http status code 42 | # response.time # time in seconds the request took 43 | # response.headers # the http headers 44 | # response.headers_hash # http headers put into a hash 45 | # response.body # the response body 46 | end 47 | 48 | hydra.queue(request) 49 | hydra.run 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | http2code 2 | ========= 3 | 4 | Author: Patrick Hof 5 | License: [CC0 1.0 Universal License](http://creativecommons.org/publicdomain/zero/1.0/legalcode) 6 | 7 | Download: git clone http://github.com/courts/http2code.git 8 | YARD docs: [http://courts.github.com/http2code](http://courts.github.com/http2code) 9 | 10 | http2code is simple templating system for translating a raw HTTP GET or POST 11 | request into source code for replaying that request. It basically reads the 12 | request from stdin and returns the parsed request as JSON data. It optionally 13 | gives you the possibility to automatically add this data to a predefined 14 | template, which will then be output to stdout. See the existing templates for 15 | further information. 16 | 17 | Command Line Usage 18 | ------------------ 19 | Usage: http2code [options] 20 | -n, --newlines Use \n as line delimiter when parsing the POST request instead of \r\n 21 | -t, --template TEMPLATE Use template TEMPLATE 22 | -b, --header-blacklist Use header blacklist to automatically remove common headers not needed 23 | -p, --no-pretty-printing Do not pretty print JSON data (may be required for some templates) 24 | -h, --help Show this help 25 | 26 | Available Templates: 27 | -------------------- 28 | ruby/net_http 29 | ruby/curb 30 | ruby/typhoeus 31 | 32 | If no template is given, only the JSON data of the parsed HTTP request will be output. 33 | 34 | Examples 35 | -------- 36 | 37 | With an HTTP GET or POST request saved in req.txt: 38 | 39 | http2code -n -t ruby/typhoeus < req.txt 40 | 41 | RubyGems 42 | -------- 43 | 44 | A gemspec file is included, so you can build and install http2code as a gem with: 45 | 46 | gem build http2code.gemspec 47 | gem install http2code-x.x.x.gem 48 | -------------------------------------------------------------------------------- /lib/http2code/request.rb: -------------------------------------------------------------------------------- 1 | # A class holding an HTTP request. The object can be initialized with a raw 2 | # request string and will parse it into simple Hash structures. 3 | # 4 | # @author Patrick Hof 5 | class Request 6 | 7 | attr_accessor :header, :body, :req 8 | 9 | HEADER_BLACKLIST = ["Host", "Accept", "Accept-Language", "Accept-Encoding", "Accept-Charset", "Keep-Alive", "Cache-Control"] 10 | 11 | # Creates a new Request object 12 | # 13 | # @param [String] data The HTTP request to parse 14 | # @option opts [String] :delimiter ('\r\n') The HTTP request's line delimiter 15 | # @option opts [Boolean] :header_bl (false) If the headers from the header blacklist should be automatically ignored 16 | def initialize(data, opts) 17 | @opts = opts 18 | header, body = data.split(@opts[:delimiter]*2, 2) 19 | self.req = {} 20 | self.header = header_to_hash(header) 21 | self.body = body_to_hash(body) 22 | end 23 | 24 | # Parses the HTTP Request header into a Hash structure 25 | # 26 | # @param [String] data The raw request headers 27 | # @return [Hash] A Hash structure holding the header values 28 | def header_to_hash data 29 | header = {} 30 | data = data.split(@opts[:delimiter]) 31 | self.req["Verb"], self.req["Url"], self.req["Version"] = data.shift.split(" ", 3) 32 | data.each do |line| 33 | k,v = line.split(":", 2) 34 | if !@opts[:header_bl] || !(HEADER_BLACKLIST.include? k) 35 | header[k] = v.lstrip 36 | end 37 | end 38 | header 39 | end 40 | 41 | # Parses the HTTP request body into a Hash structure 42 | # 43 | # @param [String] data The raw request body 44 | # @return [String] A Hash structure holding the body values. 45 | # @example 46 | # body_to_hash('aa=bb&cc=dd&cc=ee') #=> {"aa" => ["bb"], "cc" => ["dd", "ee"]} 47 | # @todo Implement different POST data types (e.g. multipart/form-data) 48 | def body_to_hash data 49 | body = {} 50 | if data 51 | data.chomp.split("&").map {|x| x.split("=")}.each do |a| 52 | if a.length == 1 53 | body[a[0]] = [] unless body.has_key?(a[0]) 54 | elsif a.length == 2 55 | if body.has_key?(a[0]) 56 | body[a[0]] << a[1] 57 | else 58 | body[a[0]] = [a[1]] 59 | end 60 | end 61 | end 62 | end 63 | body 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /bin/http2code: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require 'json' 4 | require 'optparse' 5 | require 'fileutils' 6 | require 'http2code/request' 7 | 8 | # @author Patrick Hof 9 | class Http2Code 10 | 11 | def parseopts(args) 12 | options = { 13 | :delimiter => "\r\n", 14 | :template => nil, 15 | :header_bl => false, 16 | :pretty => true 17 | } 18 | OptionParser.new(args) do |opts| 19 | opts.banner = "Usage: #{File.basename(__FILE__)} [options]" 20 | opts.on("-n", "--newlines", 'Use \n as line delimiter when parsing the POST request instead of \r\n') do |n| 21 | options[:delimiter] = "\n" 22 | end 23 | opts.on("-t", "--template TEMPLATE", 'Use template TEMPLATE') do |t| 24 | options[:template] = t 25 | end 26 | opts.on("-b", "--header-blacklist", 'Use header blacklist to automatically remove common headers not needed') do 27 | options[:header_bl] = true 28 | end 29 | opts.on("-p", "--no-pretty-printing", "Do not pretty print JSON data (may be required for some templates)") do 30 | options[:pretty] = false 31 | end 32 | opts.on("-h", "--help", "Show this help") do 33 | puts opts 34 | puts 35 | puts "Available Templates:" 36 | puts "--------------------" 37 | FileUtils.cd(File.join(File.dirname(__FILE__), '..', 'lib', 'http2code', 'templates')) do 38 | puts Dir['*/*'].join("\n") 39 | end 40 | puts 41 | puts "If no template is given, only the JSON data of the parsed HTTP request will be output." 42 | puts 43 | exit 44 | end 45 | end.parse! 46 | options 47 | end 48 | 49 | def initialize 50 | opts = parseopts(ARGV) 51 | req = Request.new($stdin.read(), opts) 52 | func = opts[:pretty] ? "pretty_generate" : "generate" 53 | req_data = JSON.send(func, req.req) 54 | header = JSON.send(func, req.header) 55 | body = JSON.send(func, req.body) 56 | if opts[:template] 57 | begin 58 | File.open(File.join(File.dirname(__FILE__), '..', 'lib', 'http2code', 'templates', opts[:template])) do |f| 59 | s = f.read() 60 | s.gsub!(/%REQ%/, req_data) 61 | s.gsub!(/%HEADER%/, header) 62 | s.gsub!(/%BODY%/, body) 63 | puts s 64 | end 65 | rescue Exception => e 66 | puts "ERROR: #{e}" 67 | exit 1 68 | end 69 | else 70 | puts "Request:" 71 | puts req_data 72 | puts 73 | puts "Header:" 74 | puts header 75 | puts 76 | puts "Body:" 77 | puts body 78 | end 79 | end 80 | end 81 | 82 | Http2Code.new 83 | --------------------------------------------------------------------------------