# -*- coding: binary -*-
require 'uri'
#require 'rex/proto/http'
require 'rex/socket'
require 'rex/text'

require 'pp'

module Rex
module Proto
module Http

class ClientRequest

	DefaultUserAgent = "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)"
	DefaultConfig = {
		#
		# Regular HTTP stuff
		#
		'agent'                  => DefaultUserAgent,
		'cgi'                    => true,
		'cookie'                 => nil,
		'data'                   => '',
		'headers'                => nil,
		'raw_headers'            => '',
		'method'                 => 'GET',
		'path_info'              => '',
		'port'                   => 80,
		'proto'                  => 'HTTP',
		'query'                  => '',
		'ssl'                    => false,
		'uri'                    => '/',
		'vars_get'               => {},
		'vars_post'              => {},
		'version'                => '1.1',
		'vhost'                  => nil,

		#
		# Evasion options
		#
		'encode_params'          => true,
		'encode'                 => false,
		'uri_encode_mode'        => 'hex-normal', # hex-all, hex-random, u-normal, u-random, u-all
		'uri_encode_count'       => 1,       # integer
		'uri_full_url'           => false,   # bool
		'pad_method_uri_count'   => 1,       # integer
		'pad_uri_version_count'  => 1,       # integer
		'pad_method_uri_type'    => 'space', # space, tab, apache
		'pad_uri_version_type'   => 'space', # space, tab, apache
		'method_random_valid'    => false,   # bool
		'method_random_invalid'  => false,   # bool
		'method_random_case'     => false,   # bool
		'version_random_valid'   => false,   # bool
		'version_random_invalid' => false,   # bool
		'version_random_case'    => false,   # bool
		'uri_dir_self_reference' => false,   # bool
		'uri_dir_fake_relative'  => false,   # bool
		'uri_use_backslashes'    => false,   # bool
		'pad_fake_headers'       => false,   # bool
		'pad_fake_headers_count' => 16,      # integer
		'pad_get_params'         => false,   # bool
		'pad_get_params_count'   => 8,       # integer
		'pad_post_params'        => false,   # bool
		'pad_post_params_count'  => 8,       # integer
		'uri_fake_end'           => false,   # bool
		'uri_fake_params_start'  => false,   # bool
		'header_folding'         => false,   # bool
		'chunked_size'           => 0,        # integer

		#
		# NTLM Options
		#
		'usentlm2_session' => true,
		'use_ntlmv2'       => true,
		'send_lm'         => true,
		'send_ntlm'       => true,
		'SendSPN'  => true,
		'UseLMKey' => false,
		'domain' => 'WORKSTATION',
		#
		# Digest Options
		#
		'DigestAuthIIS' => true
	}

	attr_reader :opts

	def initialize(opts={})
		@opts = DefaultConfig.merge(opts)
		@opts['headers'] ||= {}
	end

	def to_s

		# Start GET query string
		qstr = opts['query'] ? opts['query'].dup : ""

		# Start POST data string
		pstr = opts['data'] ? opts['data'].dup : ""

		if opts['cgi']
			uri_str = set_uri

			if (opts['pad_get_params'])
				1.upto(opts['pad_get_params_count'].to_i) do |i|
					qstr << '&' if qstr.length > 0
					qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1))
					qstr << '='
					qstr << set_encode_uri(Rex::Text.rand_text_alphanumeric(rand(32)+1))
				end
			end

			opts['vars_get'].each_pair do |var,val|
				var = var.to_s
				val = val.to_s

				qstr << '&' if qstr.length > 0
				qstr << (opts['encode_params'] ? set_encode_uri(var) : var)
				qstr << '='
				qstr << (opts['encode_params'] ? set_encode_uri(val) : val)
			end

			if (opts['pad_post_params'])
				1.upto(opts['pad_post_params_count'].to_i) do |i|
					rand_var = Rex::Text.rand_text_alphanumeric(rand(32)+1)
					rand_val = Rex::Text.rand_text_alphanumeric(rand(32)+1)
					pstr << '&' if pstr.length > 0
					pstr << (opts['encode_params'] ? set_encode_uri(rand_var) : rand_var)
					pstr << '='
					pstr << (opts['encode_params'] ? set_encode_uri(rand_val) : rand_val)
				end
			end

			opts['vars_post'].each_pair do |var,val|
				var = var.to_s
				val = val.to_s

				pstr << '&' if pstr.length > 0
				pstr << (opts['encode_params'] ? set_encode_uri(var) : var)
				pstr << '='
				pstr << (opts['encode_params'] ? set_encode_uri(val) : val)
			end
		else
			if opts['encode']
				qstr = set_encode_uri(qstr)
			end
			uri_str = set_uri
		end

		req = ''
		req << set_method
		req << set_method_uri_spacer()
		req << set_uri_prepend()

		if opts['encode']
			req << set_encode_uri(uri_str)
		else
			req << uri_str
		end


		if (qstr.length > 0)
			req << '?'
			req << qstr
		end

		req << set_path_info
		req << set_uri_append()
		req << set_uri_version_spacer()
		req << set_version
		req << set_host_header

		# If an explicit User-Agent header is set, then use that instead of
		# the default
		unless opts['headers'] and opts['headers'].keys.map{|x| x.downcase }.include?('user-agent')
			req << set_agent_header
		end

		# Similar to user-agent, only add an automatic auth header if a
		# manual one hasn't been provided
		unless opts['headers'] and opts['headers'].keys.map{|x| x.downcase }.include?('authorization')
			req << set_auth_header
		end

		req << set_cookie_header
		req << set_connection_header
		req << set_extra_headers

		req << set_content_type_header
		req << set_content_len_header(pstr.length)
		req << set_chunked_header()
		req << opts['raw_headers']
		req << set_body(pstr)
	end

	protected

	def set_uri
		uri_str = opts['uri'].dup
		if (opts['uri_dir_self_reference'])
			uri_str.gsub!('/', '/./')
		end

		if (opts['uri_dir_fake_relative'])
			buf = ""
			uri_str.split('/',-1).each do |part|
				cnt = rand(8)+2
				1.upto(cnt) { |idx|
					buf << "/" + Rex::Text.rand_text_alphanumeric(rand(32)+1)
				}
				buf << ("/.." * cnt)
				buf << "/" + part
			end
			uri_str = buf
		end

		if (opts['uri_full_url'])
			url = opts['ssl'] ? "https://" : "http://"
			url << opts['vhost']
			url << ((opts['port'] == 80) ? "" : ":#{opts['port']}")
			url << uri_str
			url
		else
			uri_str
		end
	end

	def set_encode_uri(str)
		a = str.to_s.dup
		opts['uri_encode_count'].times {
			a = Rex::Text.uri_encode(a, opts['uri_encode_mode'])
		}
		return a
	end

	def set_method
		ret = opts['method'].dup

		if (opts['method_random_valid'])
			ret = ['GET', 'POST', 'HEAD'][rand(3)]
		end

		if (opts['method_random_invalid'])
			ret = Rex::Text.rand_text_alpha(rand(20)+1)
		end

		if (opts['method_random_case'])
			ret = Rex::Text.to_rand_case(ret)
		end

		ret
	end

	def set_method_uri_spacer
		len = opts['pad_method_uri_count'].to_i
		set = " "
		buf = ""

		case opts['pad_method_uri_type']
		when 'tab'
			set = "\t"
		when 'apache'
			set = "\t \x0b\x0c\x0d"
		end

		while(buf.length < len)
			buf << set[ rand(set.length) ]
		end

		return buf
	end

	#
	# Return the padding to place before the uri
	#
	def set_uri_prepend
		prefix = ""

		if (opts['uri_fake_params_start'])
			prefix << '/%3fa=b/../'
		end

		if (opts['uri_fake_end'])
			prefix << '/%20HTTP/1.0/../../'
		end

		prefix
	end

	#
	# Return the HTTP path info
	# TODO:
	#  * Encode path information
	def set_path_info
		opts['path_info'] ? opts['path_info'] : ''
	end

	#
	# Return the padding to place before the uri
	#
	def set_uri_append
		# TODO:
		#  * Support different padding types
		""
	end

	#
	# Return the spacing between the uri and the version
	#
	def set_uri_version_spacer
		len = opts['pad_uri_version_count'].to_i
		set = " "
		buf = ""

		case opts['pad_uri_version_type']
		when 'tab'
			set = "\t"
		when 'apache'
			set = "\t \x0b\x0c\x0d"
		end

		while(buf.length < len)
			buf << set[ rand(set.length) ]
		end

		return buf
	end

	#
	# Return the HTTP version string
	#
	def set_version
		ret = opts['proto'] + "/" + opts['version']

		if (opts['version_random_valid'])
			ret = opts['proto'] + "/" +  ['1.0', '1.1'][rand(2)]
		end

		if (opts['version_random_invalid'])
			ret = Rex::Text.rand_text_alphanumeric(rand(20)+1)
		end

		if (opts['version_random_case'])
			ret = Rex::Text.to_rand_case(ret)
		end

		ret << "\r\n"
	end

	#
	# Return a formatted header string
	#
	def set_formatted_header(var, val)
		if (self.opts['header_folding'])
			"#{var}:\r\n\t#{val}\r\n"
		else
			"#{var}: #{val}\r\n"
		end
	end

	#
	# Return the HTTP agent header
	#
	def set_agent_header
		opts['agent'] ? set_formatted_header("User-Agent", opts['agent']) : ""
	end

	def set_auth_header
		opts['authorization'] ? set_formatted_header("Authorization", opts['authorization']) : ""
	end

	#
	# Return the HTTP cookie header
	#
	def set_cookie_header
		opts['cookie'] ? set_formatted_header("Cookie", opts['cookie']) : ""
	end

	#
	# Return the HTTP connection header
	#
	def set_connection_header
		opts['connection'] ? set_formatted_header("Connection", opts['connection']) : ""
	end

	#
	# Return the content type header
	#
	def set_content_type_header
		opts['ctype'] ? set_formatted_header("Content-Type", opts['ctype']) : ""
	end

	#
	# Return the content length header
	def set_content_len_header(clen)
		return "" if opts['chunked_size'] > 0
		set_formatted_header("Content-Length", clen)
	end

	#
	# Return the HTTP Host header
	#
	def set_host_header
		return "" if opts['uri_full_url']
		host = opts['vhost']

		# IPv6 addresses must be placed in brackets
		if Rex::Socket.is_ipv6?(host)
			host = "[#{host}]"
		end

		# The port should be appended if non-standard
		if not [80,443].include?(opts['port'])
			host = host + ":#{opts['port']}"
		end

		set_formatted_header("Host", host)
	end

	#
	# Return a string of formatted extra headers
	#
	def set_extra_headers
		buf = ''

		if (opts['pad_fake_headers'])
			1.upto(opts['pad_fake_headers_count'].to_i) do |i|
				buf << set_formatted_header(
					Rex::Text.rand_text_alphanumeric(rand(32)+1),
					Rex::Text.rand_text_alphanumeric(rand(32)+1)
				)
			end
		end

		opts['headers'].each_pair do |var,val|
			buf << set_formatted_header(var, val)
		end

		buf
	end

	def set_chunked_header
		return "" if opts['chunked_size'] == 0
		set_formatted_header('Transfer-Encoding', 'chunked')
	end

	#
	# Return the HTTP seperator and body string
	#
	def set_body(bdata)
		return "\r\n" + bdata if opts['chunked_size'] == 0
		str = bdata.dup
		chunked = ''
		while str.size > 0
			chunk = str.slice!(0,rand(opts['chunked_size']) + 1)
			chunked << sprintf("%x", chunk.size) + "\r\n" + chunk + "\r\n"
		end
		"\r\n" + chunked + "0\r\n\r\n"
	end


end



end
end
end
