git-svn-id: file:///home/svn/framework3/trunk@7652 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
1189ac1dcd
commit
ed7b7ac6f0
|
@ -96,8 +96,25 @@ class Client
|
|||
#
|
||||
def set_config(opts = {})
|
||||
opts.each_pair do |var,val|
|
||||
typ = self.config_types[var] || 'string'
|
||||
|
||||
if(typ.class.to_s == 'Array')
|
||||
if not typ.include?(val)
|
||||
raise RuntimeError, "The specified value for #{var} is not one of the valid choices"
|
||||
end
|
||||
end
|
||||
|
||||
if(typ == 'bool')
|
||||
val = (val =~ /^(t|y|1)$/i ? true : false)
|
||||
end
|
||||
|
||||
if(typ == 'integer')
|
||||
val = val.to_i
|
||||
end
|
||||
|
||||
self.config[var]=val
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -113,7 +130,7 @@ class Client
|
|||
c_qs = opts['query']
|
||||
c_ag = opts['agent'] || config['agent']
|
||||
c_cook = opts['cookie'] || config['cookie']
|
||||
c_host = opts['vhost'] || config['vhost']
|
||||
c_host = opts['vhost'] || config['vhost'] || self.host
|
||||
c_head = opts['headers'] || config['headers'] || {}
|
||||
c_rawh = opts['raw_headers']|| config['raw_headers'] || ''
|
||||
c_conn = opts['connection']
|
||||
|
@ -293,19 +310,10 @@ class Client
|
|||
|
||||
#
|
||||
# Send a HTTP request to the server
|
||||
# TODO:
|
||||
# * Handle junk pipeline requests
|
||||
#
|
||||
def send_request(req)
|
||||
# Connect to the server
|
||||
connect
|
||||
|
||||
# build the request
|
||||
req_string = req.to_s
|
||||
|
||||
# Send it on over
|
||||
ret = conn.put(req_string)
|
||||
|
||||
ret
|
||||
conn.put(req.to_s)
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -316,81 +324,38 @@ class Client
|
|||
resp = Response.new
|
||||
resp.max_data = config['read_max_data']
|
||||
|
||||
# Tell the remote side if we aren't pipelining
|
||||
#conn.shutdown(::Socket::SHUT_WR) if (!pipelining?)
|
||||
|
||||
# Wait at most t seconds for the full response to be read in. We only
|
||||
# do this if t was specified as a negative value indicating an infinite
|
||||
# wait cycle. If t were specified as nil it would indicate that no
|
||||
# response parsing is required.
|
||||
Timeout.timeout((t < 0) ? nil : t) {
|
||||
# Now, read in the response until we're good to go.
|
||||
|
||||
return resp if not t
|
||||
|
||||
Timeout.timeout((t < 0) ? nil : t) do
|
||||
|
||||
rv = nil
|
||||
while (
|
||||
rv != Packet::ParseCode::Completed and
|
||||
rv != Packet::ParseCode::Error
|
||||
)
|
||||
begin
|
||||
if self.junk_pipeline
|
||||
i = 0
|
||||
self.junk_pipeline.times {
|
||||
i += 1
|
||||
rv = nil
|
||||
buff = conn.get
|
||||
rv = resp.parse( buff || '')
|
||||
|
||||
while rv != Packet::ParseCode::Completed
|
||||
if (rv == Packet::ParseCode::Error)
|
||||
return
|
||||
# Handle unexpected disconnects
|
||||
rescue ::Errno::EPIPE, ::EOFError, ::IOError
|
||||
case resp.state
|
||||
when Packet::ParseState::ProcessingHeader
|
||||
resp = nil
|
||||
when Packet::ParseState::ProcessingBody
|
||||
# truncated request, good enough
|
||||
resp.error = :truncated
|
||||
end
|
||||
|
||||
if resp.bufq.length > 0
|
||||
rv = resp.parse('')
|
||||
else
|
||||
rv = resp.parse(conn.get)
|
||||
end
|
||||
end
|
||||
|
||||
if resp['Connection'] == 'close'
|
||||
return
|
||||
end
|
||||
|
||||
buf = resp.bufq
|
||||
resp.reset
|
||||
resp.bufq = buf
|
||||
}
|
||||
end
|
||||
|
||||
rv = nil
|
||||
if resp.bufq.length > 0
|
||||
rv = resp.parse('')
|
||||
end
|
||||
|
||||
if rv != Packet::ParseCode::Completed
|
||||
# Keep running until we finish parsing or EOF is reached
|
||||
while ((rv = resp.parse(conn.get)) != Packet::ParseCode::Completed)
|
||||
# Parsing error? Raise an exception, our job is done.
|
||||
if (rv == Packet::ParseCode::Error)
|
||||
break
|
||||
end
|
||||
select(nil, nil, nil, 0.10)
|
||||
end
|
||||
end
|
||||
rescue EOFError
|
||||
return nil
|
||||
rescue ::Timeout::Error
|
||||
#$stdout.puts("timeout\n")
|
||||
end
|
||||
} if (t)
|
||||
|
||||
# Close our side if we aren't pipelining
|
||||
#close if (!pipelining?)
|
||||
|
||||
# if the server said stop pipelining, we listen...
|
||||
if resp['Connection'] == 'close'
|
||||
#close
|
||||
end
|
||||
|
||||
# XXX - How should we handle this?
|
||||
if (not resp.completed?)
|
||||
# raise RuntimeError, resp.error, caller
|
||||
end
|
||||
|
||||
# Always return the Response object back to the client
|
||||
return resp
|
||||
resp
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -461,7 +426,7 @@ class Client
|
|||
if (self.config['uri_full_url'])
|
||||
url = self.ssl ? "https" : "http"
|
||||
url << self.config['vhost']
|
||||
url << (self.port == 80) ? "" : ":#{self.port}"
|
||||
url << ((self.port == 80) ? "" : ":#{self.port}")
|
||||
url << uri
|
||||
url
|
||||
else
|
||||
|
|
|
@ -26,7 +26,7 @@ class Rex::Proto::Http::Client::UnitTest < Test::Unit::TestCase
|
|||
)
|
||||
|
||||
#
|
||||
# Request the main web pagfe
|
||||
# Request the main web page
|
||||
#
|
||||
r = c.request_raw(
|
||||
'method' => 'GET',
|
||||
|
@ -75,17 +75,19 @@ class Rex::Proto::Http::Client::UnitTest < Test::Unit::TestCase
|
|||
end
|
||||
|
||||
def test_ssl
|
||||
c = Klass.new('www.geotrust.com', 443, {}, true)
|
||||
c.set_config('vhost' => 'www.geotrust.com')
|
||||
c = Klass.new('www.metasploit.com', 443, {}, true)
|
||||
c.set_config('vhost' => 'www.metasploit.com')
|
||||
r = c.request_raw(
|
||||
'method' => 'GET',
|
||||
'uri' => '/'
|
||||
)
|
||||
resp = c.send_recv(r)
|
||||
|
||||
assert_equal(200, resp.code)
|
||||
assert_equal('OK', resp.message)
|
||||
assert_equal('1.1', resp.proto)
|
||||
assert_equal('1.0', resp.proto)
|
||||
c.close
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
|
|
|
@ -74,16 +74,16 @@ class Packet
|
|||
def parse(buf)
|
||||
|
||||
# Append the incoming buffer to the buffer queue.
|
||||
self.bufq += buf
|
||||
self.bufq += buf.to_s
|
||||
|
||||
begin
|
||||
# If we're processing headers, do that now.
|
||||
|
||||
# Process the header
|
||||
if(self.state == ParseState::ProcessingHeader)
|
||||
parse_header_re
|
||||
parse_header
|
||||
end
|
||||
|
||||
# If we're processing the body (possibly after having finished
|
||||
# processing headers), do that now.
|
||||
# Continue on to the body if the header was processed
|
||||
if(self.state == ParseState::ProcessingBody)
|
||||
if (self.body_bytes_left == 0)
|
||||
self.state = ParseState::Completed
|
||||
|
@ -104,33 +104,30 @@ class Packet
|
|||
# Reset the parsing state and buffers.
|
||||
#
|
||||
def reset
|
||||
self.bufq = ''
|
||||
self.state = ParseState::ProcessingHeader
|
||||
self.transfer_chunked = false
|
||||
self.inside_chunk = false
|
||||
self.headers.reset
|
||||
self.bufq = ''
|
||||
self.body = ''
|
||||
self.transfer_chunked = nil
|
||||
self.inside_chunk = nil
|
||||
end
|
||||
|
||||
#
|
||||
# Returns whether or not parsing has completed.
|
||||
#
|
||||
def completed?
|
||||
comp = false
|
||||
|
||||
return true if self.state == ParseState::Completed
|
||||
|
||||
# If the parser state is processing the body and there are an
|
||||
# undetermined number of bytes left to read, we just need to say that
|
||||
# things are completed as it's hard to tell whether or not they really
|
||||
# are.
|
||||
if ((self.state == ParseState::ProcessingBody) and
|
||||
(self.body_bytes_left < 0) )
|
||||
comp = true
|
||||
# Or, if the parser state actually is completed, then we're good.
|
||||
elsif (self.state == ParseState::Completed)
|
||||
comp = true
|
||||
if (self.state == ParseState::ProcessingBody and self.body_bytes_left < 0)
|
||||
return true
|
||||
end
|
||||
|
||||
return comp
|
||||
false
|
||||
end
|
||||
|
||||
#
|
||||
|
@ -197,7 +194,6 @@ class Packet
|
|||
#
|
||||
def from_s(str)
|
||||
reset
|
||||
|
||||
parse(str)
|
||||
end
|
||||
|
||||
|
@ -215,7 +211,7 @@ class Packet
|
|||
end
|
||||
|
||||
attr_reader :headers
|
||||
attr_reader :error
|
||||
attr_accessor :error
|
||||
attr_accessor :state
|
||||
attr_accessor :bufq
|
||||
attr_accessor :body
|
||||
|
@ -231,10 +227,10 @@ class Packet
|
|||
protected
|
||||
|
||||
attr_writer :headers
|
||||
attr_writer :error
|
||||
attr_writer :incomplete
|
||||
attr_accessor :body_bytes_left
|
||||
attr_accessor :inside_chunk
|
||||
attr_accessor :keepalive
|
||||
|
||||
##
|
||||
#
|
||||
|
@ -254,100 +250,48 @@ protected
|
|||
#
|
||||
##
|
||||
|
||||
def parse_header_re
|
||||
m = /(.*?)\r?\n\r?\n(.*)/smi.match(self.bufq)
|
||||
if m != nil
|
||||
self.headers.from_s(m[1])
|
||||
self.bufq = m[2]
|
||||
|
||||
# Extract the content length, if any.
|
||||
if (self.headers['Content-Length'])
|
||||
self.body_bytes_left = self.headers['Content-Length'].to_i
|
||||
else
|
||||
self.body_bytes_left = self.bufq.length
|
||||
end
|
||||
|
||||
if (self.headers['Transfer-Encoding'])
|
||||
self.transfer_chunked = 1 if self.headers['Transfer-Encoding'] =~ /chunked/i
|
||||
end
|
||||
|
||||
connection = self.headers['Connection']
|
||||
|
||||
comp_on_close = false
|
||||
if (connection and connection.downcase == 'close')
|
||||
comp_on_close = true
|
||||
end
|
||||
|
||||
# Change states to processing the body if we have a content length or
|
||||
# the connection type is close.
|
||||
if ((self.body_bytes_left > 0) or self.transfer_chunked)
|
||||
self.state = ParseState::ProcessingBody
|
||||
else
|
||||
self.state = ParseState::Completed
|
||||
end
|
||||
else
|
||||
self.headers.from_s(self.bufq)
|
||||
end
|
||||
|
||||
# No command string? Wack.
|
||||
if (self.headers.cmd_string == nil)
|
||||
raise RuntimeError, "Invalid command string", caller
|
||||
end
|
||||
|
||||
# Allow derived classes to update the parts of the command string
|
||||
self.update_cmd_parts(self.headers.cmd_string)
|
||||
end
|
||||
|
||||
#
|
||||
# Parses the header portion of the request.
|
||||
#
|
||||
def parse_header
|
||||
|
||||
# Does the buffer queue contain the entire header? If so, parse it and
|
||||
# transition to the body parsing phase.
|
||||
idx = self.bufq.index(/\r?\n\r?\n/)
|
||||
head,data = self.bufq.split(/\r?\n\r?\n/, 2)
|
||||
return if not data
|
||||
|
||||
if (idx and idx >= 0)
|
||||
# Extract the header block
|
||||
head = self.bufq.slice!(0, idx + 4)
|
||||
|
||||
# Serialize the headers
|
||||
self.headers.from_s(head)
|
||||
self.bufq = data || ""
|
||||
|
||||
# Extract the content length, if any.
|
||||
# Set the content-length to -1 as a placeholder (read until EOF)
|
||||
self.body_bytes_left = -1
|
||||
|
||||
# Extract the content length if it was specified
|
||||
if (self.headers['Content-Length'])
|
||||
self.body_bytes_left = self.headers['Content-Length'].to_i
|
||||
end
|
||||
|
||||
# Look for a chunked transfer header
|
||||
if (self.headers['Transfer-Encoding'].to_s.downcase == 'chunked')
|
||||
self.transfer_chunked = true
|
||||
end
|
||||
|
||||
# Determine how to handle data when there is no length header
|
||||
if(self.body_bytes_left == -1 and self.transfer_chunked != true)
|
||||
if(self.headers['Connection'].to_s.downcase == 'keep-alive')
|
||||
# If we are using keep-alive, but have no content-length and
|
||||
# no chunked transfer header, pretend this is the entire
|
||||
# buffer and call it done
|
||||
self.body_bytes_left = self.bufq.length
|
||||
else
|
||||
# Otherwise we need to keep reading until EOF
|
||||
self.body_bytes_left = -1
|
||||
end
|
||||
|
||||
if (self.headers['Transfer-Encoding'])
|
||||
self.transfer_chunked = 1 if self.headers['Transfer-Encoding'] =~ /chunked/i
|
||||
end
|
||||
|
||||
connection = self.headers['Connection']
|
||||
comp_on_close = false
|
||||
|
||||
if (connection and connection == 'close')
|
||||
comp_on_close = true
|
||||
end
|
||||
|
||||
# Change states to processing the body if we have a content length or
|
||||
# the connection type is close.
|
||||
if ((self.body_bytes_left > 0) or (comp_on_close) or self.transfer_chunked)
|
||||
self.state = ParseState::ProcessingBody
|
||||
else
|
||||
self.state = ParseState::Completed
|
||||
end
|
||||
else
|
||||
return ParseState::ProcessingHeader
|
||||
end
|
||||
|
||||
# No command string? Wack.
|
||||
if (self.headers.cmd_string == nil)
|
||||
# Throw an error if we didnt parse the header properly
|
||||
if !self.headers.cmd_string
|
||||
raise RuntimeError, "Invalid command string", caller
|
||||
end
|
||||
|
||||
# Move the state into body processing
|
||||
self.state = ParseState::ProcessingBody
|
||||
|
||||
# Allow derived classes to update the parts of the command string
|
||||
self.update_cmd_parts(self.headers.cmd_string)
|
||||
end
|
||||
|
@ -373,7 +317,13 @@ protected
|
|||
clen.rstrip! if (clen)
|
||||
|
||||
# if we happen to fall upon the end of the buffer for the next chunk len and have no data left, go get some more...
|
||||
if clen == nil and self.bufq.length == 0
|
||||
if clen.nil? and self.bufq.length == 0
|
||||
return
|
||||
end
|
||||
|
||||
# Invalid chunk length, exit out early
|
||||
if clen.nil?
|
||||
self.state = ParseState::Completed
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -421,3 +371,4 @@ end
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in New Issue