#
# Copyright (c) 2006-2012 Hal Brodigan (postmodern.mod3 at gmail.com)
#
# This file is part of Ronin Support.
#
# Ronin Support is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ronin Support is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with Ronin Support. If not, see .
#
require 'ronin/network/http/exceptions/unknown_request'
require 'ronin/network/http/proxy'
require 'ronin/network/ssl'
require 'uri/query_params'
require 'net/http'
begin
require 'net/https'
rescue ::LoadError
end
module Ronin
module Network
#
# Provides helper methods for communicating with HTTP Servers.
#
module HTTP
#
# The Ronin HTTP proxy to use.
#
# @return [Proxy]
# The Ronin HTTP proxy.
#
# @note
# If the `HTTP_PROXY` environment variable is specified, it will
# be used as the default value.
#
# @see Proxy.new
# @see Proxy.parse
#
# @api public
#
def self.proxy
@proxy ||= if ENV['HTTP_PROXY']
Proxy.parse(ENV['HTTP_PROXY'])
else
Proxy.new
end
end
#
# Sets the Ronin HTTP proxy to use.
#
# @param [Proxy, URI::HTTP, Hash, String] new_proxy
# The new proxy information to use.
#
# @return [Proxy]
# The new proxy.
#
# @raise [ArgumentError]
# The given proxy information was not a {Proxy}, `URI::HTTP`,
# `Hash` or {String}.
#
# @api public
#
def self.proxy=(new_proxy)
@proxy = Proxy.create(new_proxy)
end
#
# The default Ronin HTTP User-Agent string.
#
# @return [String, nil]
# The default Ronin HTTP User-Agent.
#
# @api public
#
def self.user_agent
@user_agent ||= nil
end
#
# Sets the default Ronin HTTP User-Agent string.
#
# @param [String] agent
# The new User-Agent string to use.
#
# @api public
#
def self.user_agent=(agent)
@user_agent = agent
end
#
# Expands the URL into options.
#
# @param [URI::HTTP, String] url
# The URL to expand.
#
# @return [Hash{Symbol => Object}]
# The options for the URL.
#
# @api private
#
def self.expand_url(url)
new_options = {}
url = case url
when URI
url
when Hash
URI::HTTP.build(url)
else
URI(url.to_s)
end
new_options[:ssl] = {} if url.scheme == 'https'
new_options[:host] = url.host
new_options[:port] = url.port
new_options[:user] = url.user if url.user
new_options[:password] = url.password if url.password
new_options[:path] = unless url.path.empty?
url.path
else
'/'
end
new_options[:path] += "?#{url.query}" if url.query
return new_options
end
#
# Expands the given HTTP options.
#
# @param [Hash] options
# HTTP options.
#
# @option options [String, URI::HTTP, URI::HTTPS] :url
# The URL to request.
#
# @option options [String] :host
# The host to connect to.
#
# @option options [String] :port (Net::HTTP.default_port)
# The port to connect to.
#
# @option options [String] :user
# The user to authenticate as.
#
# @option options [String] :password
# The password to authenticate with.
#
# @option options [String] :path ('/')
# The path to request.
#
# @option options [String, Hash] :proxy (HTTP.proxy)
# The Proxy information.
#
# @return [Hash]
# The expanded version of options.
#
# @api private
#
def self.expand_options(options={})
new_options = options.dup
new_options[:port] ||= Net::HTTP.default_port
new_options[:path] ||= '/'
if new_options[:ssl] == true
new_options[:ssl] = {}
end
if (url = new_options.delete(:url))
new_options.merge!(HTTP.expand_url(url))
end
new_options[:proxy] = if new_options.has_key?(:proxy)
HTTP::Proxy.create(new_options[:proxy])
else
HTTP.proxy
end
return new_options
end
#
# Converts an underscored, dashed, lowercase or uppercase HTTP header
# name to the standard camel-case HTTP header name.
#
# @param [Symbol, String] name
# The unformatted HTTP header name.
#
# @return [String]
# The camel-case HTTP header name.
#
# @api private
#
def self.header_name(name)
words = name.to_s.split(/[\s+_-]/)
words.each { |word| word.capitalize! }
return words.join('-')
end
#
# Converts underscored, dashed, lowercase and uppercase HTTP headers
# to standard camel-cased HTTP headers.
#
# @param [Hash{Symbol,String => String}] options
# Ronin HTTP headers.
#
# @return [Hash]
# The camel-cased HTTP headers created from the given options.
#
# @api private
#
def self.headers(options={})
headers = {}
if user_agent
headers['User-Agent'] = user_agent
end
if options
options.each do |name,value|
headers[HTTP.header_name(name)] = value.to_s
end
end
return headers
end
#
# Creates a specific type of HTTP request object.
#
# @param [Hash] options
# The HTTP options for the request.
#
# @option options [Symbol, String] :method
# The HTTP method to use for the request.
#
# @option options [String] :path ('/')
# The path to request.
#
# @option options [String] :query
# The query-string to append to the request path.
#
# @option options [String] :query_params
# The query-params to append to the request path.
#
# @option options [String] :body
# The body of the request.
#
# @option options [Hash, String] :form_data
# The form data that may be sent in the body of the request.
#
# @option options [String] :user
# The user to authenticate as.
#
# @option options [String] :password
# The password to authenticate with.
#
# @option options [Hash{Symbol,String => String}] :headers
# Additional HTTP headers to use for the request.
#
# @return [HTTP::Request]
# The new HTTP Request object.
#
# @raise [ArgumentError]
# The `:method` option must be specified.
#
# @raise [UnknownRequest]
# The `:method` option did not match a known Net::HTTP request
# class.
#
# @see HTTP.expand_options
#
# @api private
#
def self.request(options={})
unless options[:method]
raise(ArgumentError,"the :method option must be specified")
end
name = options[:method].to_s.capitalize
unless Net::HTTP.const_defined?(name)
raise(UnknownRequest,"unknown HTTP request type #{name.dump}")
end
headers = headers(options[:headers])
path = (options[:path] || '/').to_s
query = if options[:query]
URI.escape(options[:query])
elsif options[:query_params]
URI::QueryParams.dump(options[:query_params])
end
if query
# append the query-string onto the path
path += if path.include?('?')
"{query}"
else
"?#{query}"
end
end
request = Net::HTTP.const_get(name).new(path,headers)
if request.request_body_permitted?
if options[:form_data]
request.set_form_data(options[:form_data])
elsif options[:body]
request.body = options[:body]
end
end
if options[:user]
user = options[:user].to_s
password = if options[:password]
options[:password].to_s
end
request.basic_auth(user,password)
end
return request
end
#
# Starts a HTTP connection with the server.
#
# @param [Hash] options
# Additional options
#
# @option options [String, URI::HTTP] :url
# The full URL to request.
#
# @option options [String] :host
# The host the HTTP server is running on.
#
# @option options [Integer] :port (Net::HTTP.default_port)
# The port the HTTP server is listening on.
#
# @option options [String, Hash] :proxy (HTTP.proxy)
# A Hash of proxy settings to use when connecting to the HTTP server.
#
# @option options [String] :user
# The user to authenticate with when connecting to the HTTP server.
#
# @option options [String] :password
# The password to authenticate with when connecting to the HTTP server.
#
# @option options [Boolean, Hash] :ssl
# Enables SSL for the HTTP connection.
#
# @option :ssl [Symbol] :verify
# Specifies the SSL certificate verification mode.
#
# @yield [http]
# If a block is given, it will be passed the newly created HTTP
# session object.
#
# @yieldparam [Net::HTTP] http
# The newly created HTTP session.
#
# @return [Net::HTTP]
# The HTTP session object.
#
# @api public
#
def http_connect(options={},&block)
options = HTTP.expand_options(options)
host = options[:host].to_s
port = options[:port]
proxy = options[:proxy]
proxy_host = if (proxy && proxy[:host])
proxy[:host].to_s
end
http = Net::HTTP::Proxy(
proxy_host,
proxy[:port],
proxy[:user],
proxy[:password]
).new(host,port)
if options[:ssl]
http.use_ssl = true
http.verify_mode = SSL::VERIFY[options[:ssl][:verify]]
end
http.start()
if block
if block.arity == 2
block.call(http,options)
else
block.call(http)
end
end
return http
end
#
# Creates a new temporary HTTP session with the server.
#
# @param [Hash] options
# Additional options
#
# @option options [String, URI::HTTP] :url
# The full URL to request.
#
# @option options [String] :host
# The host the HTTP server is running on.
#
# @option options [Integer] :port (Net::HTTP.default_port)
# The port the HTTP server is listening on.
#
# @option options [String] :user
# The user to authenticate with when connecting to the HTTP server.
#
# @option options [String] :password
# The password to authenticate with when connecting to the HTTP server.
#
# @option options [String, Hash] :proxy (HTTP.proxy)
# A Hash of proxy settings to use when connecting to the HTTP server.
#
# @option options [Boolean, Hash] :ssl
# Enables SSL for the HTTP connection.
#
# @option :ssl [Symbol] :verify
# Specifies the SSL certificate verification mode.
#
# @yield [http]
# If a block is given, it will be passed the newly created HTTP
# session object.
#
# @yieldparam [Net::HTTP] http
# The newly created HTTP session.
#
# @return [nil]
#
# @see #http_connect
#
# @api public
#
def http_session(options={},&block)
http_connect(options) do |http,expanded_options|
if block
if block.arity == 2
block.call(http,expanded_options)
else
block.call(http)
end
end
http.finish
end
return nil
end
#
# Connects to the HTTP server and sends an HTTP Request.
#
# @param [Hash] options
# Additional options.
#
# @option options [Symbol, String] :method
# The HTTP method to use in the request.
#
# @option options [String] :path
# The path to request from the HTTP server.
#
# @option options [Hash] :headers
# The Hash of the HTTP headers to send with the request.
# May contain either Strings or Symbols, lower-case or camel-case keys.
#
# @option options [String] :body
# The body of the request.
#
# @option options [Hash, String] :form_data
# The form data that may be sent in the body of the request.
#
# @yield [request, (options)]
# If a block is given, it will be passed the HTTP request object.
# If the block has an arity of 2, it will also be passed the expanded
# version of the given _options_.
#
# @yieldparam [Net::HTTP::Request] request
# The HTTP request object to use in the request.
#
# @yieldparam [Hash] options
# The expanded version of the given _options_.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_session
#
# @api public
#
def http_request(options={},&block)
response = nil
http_session(options) do |http,expanded_options|
req = HTTP.request(expanded_options)
if block
if block.arity == 2
block.call(req,expanded_options)
else
block.call(req)
end
end
response = http.request(req)
end
return response
end
#
# Returns the Status Code of the Response.
#
# @param [Hash] options
# Additional options.
#
# @option options [Symbol, String] :method (:head)
# The method to use for the request.
#
# @return [Integer]
# The HTTP Response Status.
#
# @see #http_request
#
# @since 0.2.0
#
# @api public
#
def http_status(options={})
options = {:method => :head}.merge(options)
return http_request(options).code.to_i
end
#
# Checks if the response has an HTTP OK status code.
#
# @param [Hash] options
# Additional options.
#
# @option options [Symbol, String] :method (:head)
# The method to use for the request.
#
# @return [Boolean]
# Specifies whether the response had an HTTP OK status code or not.
#
# @see #http_status
#
# @api public
#
def http_ok?(options={})
http_status(options) == 200
end
#
# Sends a HTTP Head request and returns the HTTP Server header.
#
# @param [Hash] options
# Additional options.
#
# @option options [Symbol, String] :method (:head)
# The method to use for the request.
#
# @return [String]
# The HTTP `Server` header.
#
# @see #http_request
#
# @api public
#
def http_server(options={})
options = {:method => :head}.merge(options)
return http_request(options)['server']
end
#
# Sends an HTTP Head request and returns the HTTP X-Powered-By header.
#
# @param [Hash] options
# Additional options.
#
# @option options [Symbol, String] :method (:get)
# The method to use for the request.
#
# @return [String]
# The HTTP `X-Powered-By` header.
#
# @see #http_request
#
# @api public
#
def http_powered_by(options={})
options = {:method => :get}.merge(options)
return http_request(options)['x-powered-by']
end
#
# Performs an HTTP Copy request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received
# from the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_copy(options={})
response = http_request(options.merge(:method => :copy))
yield response if block_given?
return response
end
#
# Performs an HTTP Delete request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_delete(options={},&block)
original_headers = options[:headers]
# set the HTTP Depth header
options[:headers] = {:depth => 'Infinity'}
if original_headers
options[:header].merge!(original_headers)
end
response = http_request(options.merge(:method => :delete))
yield response if block_given?
return response
end
#
# Performs an HTTP Get request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_get(options={},&block)
response = http_request(options.merge(:method => :get))
yield response if block_given?
return response
end
#
# Performs an HTTP Get request and returns the Response Headers.
#
# @param [Hash] options
# Additional options.
#
# @return [Hash{String => Array}]
# The Headers of the HTTP response.
#
# @see #http_get
#
# @since 0.2.0
#
# @api public
#
def http_get_headers(options={})
headers = {}
http_get(options).each_header do |name,value|
headers[HTTP.header_name(name)] = value
end
return headers
end
#
# Performs an HTTP Get request and returns the Respond Body.
#
# @param [Hash] options
# Additional options.
#
# @return [String]
# The body of the HTTP response.
#
# @see #http_get
#
# @api public
#
def http_get_body(options={})
http_get(options).body
end
#
# Performs an HTTP Head request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_head(options={},&block)
response = http_request(options.merge(:method => :head))
yield response if block_given?
return response
end
#
# Performs an HTTP Lock request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_lock(options={},&block)
response = http_request(options.merge(:method => :lock))
yield response if block_given?
return response
end
#
# Performs an HTTP Mkcol request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_mkcol(options={},&block)
response = http_request(options.merge(:method => :mkcol))
yield response if block_given?
return response
end
#
# Performs an HTTP Move request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_move(options={},&block)
response = http_request(options.merge(:method => :move))
yield response if block_given?
return response
end
#
# Performs an HTTP Options request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_options(options={},&block)
response = http_request(options.merge(:method => :options))
yield response if block_given?
return response
end
#
# Performs an HTTP Post request.
#
# @param [Hash] options
# Additional options.
#
# @option options [Hash, String] :form_data
# The form data to send with the HTTP Post request.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_post(options={},&block)
response = http_request(options.merge(:method => :post))
yield response if block_given?
return response
end
#
# Performs an HTTP Post request and returns the Response Headers.
#
# @param [Hash] options
# Additional options.
#
# @option options [Hash, String] :form_data
# The form data to send with the HTTP Post request.
#
# @return [Hash{String => Array}]
# The headers of the HTTP response.
#
# @see #http_post
#
# @since 0.2.0
#
# @api public
#
def http_post_headers(options={})
headers = {}
http_post(options).each_header do |name,value|
headers[HTTP.header_name(name)] = value
end
return headers
end
#
# Performs an HTTP Post request and returns the Response Body.
#
# @param [Hash] options
# Additional options.
#
# @option options [Hash, String] :form_data
# The form data to send with the HTTP Post request.
#
# @return [String]
# The body of the HTTP response.
#
# @see #http_post
#
# @api public
#
def http_post_body(options={})
http_post(options).body
end
#
# Performs an HTTP PUT request.
#
# @param [Hash] options
# Additional options.
#
# @option options [String] :body
# The body for the request.
#
# @option options [Hash, String] :form_data
# The form data to send with the HTTP PUT request.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @since 0.4.0
#
# @api public
#
def http_put(options={})
response = http_request(options.merge(:method => :put))
yield response if block_given?
return response
end
#
# Performs an HTTP Propfind request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_prop_find(options={},&block)
original_headers = options[:headers]
# set the HTTP Depth header
options[:headers] = {:depth => '0'}
if original_headers
options[:header].merge!(original_headers)
end
response = http_request(options.merge(:method => :propfind))
yield response if block_given?
return response
end
#
# Performs an HTTP Proppatch request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_prop_patch(options={},&block)
response = http_request(options.merge(:method => :proppatch))
yield response if block_given?
return response
end
#
# Performs an HTTP Trace request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_trace(options={},&block)
response = http_request(options.merge(:method => :trace))
yield response if block_given?
return response
end
#
# Performs an HTTP Unlock request.
#
# @param [Hash] options
# Additional options.
#
# @yield [response]
# If a block is given, it will be passed the response received from
# the request.
#
# @yieldparam [Net::HTTP::Response] response
# The HTTP response object.
#
# @return [Net::HTTP::Response]
# The response of the HTTP request.
#
# @see #http_request
#
# @api public
#
def http_unlock(options={},&block)
response = http_request(options.merge(:method => :unlock))
yield response if block_given?
return response
end
end
end
end