raptor-io 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/LICENSE +30 -0
- data/README.md +51 -0
- data/lib/rack/handler/raptor-io.rb +130 -0
- data/lib/raptor-io.rb +11 -0
- data/lib/raptor-io/error.rb +19 -0
- data/lib/raptor-io/protocol.rb +6 -0
- data/lib/raptor-io/protocol/error.rb +10 -0
- data/lib/raptor-io/protocol/http.rb +34 -0
- data/lib/raptor-io/protocol/http/client.rb +685 -0
- data/lib/raptor-io/protocol/http/error.rb +16 -0
- data/lib/raptor-io/protocol/http/headers.rb +132 -0
- data/lib/raptor-io/protocol/http/message.rb +67 -0
- data/lib/raptor-io/protocol/http/request.rb +307 -0
- data/lib/raptor-io/protocol/http/request/manipulator.rb +117 -0
- data/lib/raptor-io/protocol/http/request/manipulators.rb +217 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticator.rb +110 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/basic.rb +36 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/digest.rb +135 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/negotiate.rb +69 -0
- data/lib/raptor-io/protocol/http/request/manipulators/authenticators/ntlm.rb +29 -0
- data/lib/raptor-io/protocol/http/request/manipulators/redirect_follower.rb +65 -0
- data/lib/raptor-io/protocol/http/response.rb +166 -0
- data/lib/raptor-io/protocol/http/server.rb +446 -0
- data/lib/raptor-io/ruby.rb +4 -0
- data/lib/raptor-io/ruby/hash.rb +24 -0
- data/lib/raptor-io/ruby/ipaddr.rb +15 -0
- data/lib/raptor-io/ruby/openssl.rb +23 -0
- data/lib/raptor-io/ruby/string.rb +27 -0
- data/lib/raptor-io/socket.rb +175 -0
- data/lib/raptor-io/socket/comm.rb +143 -0
- data/lib/raptor-io/socket/comm/local.rb +94 -0
- data/lib/raptor-io/socket/comm/sapni.rb +75 -0
- data/lib/raptor-io/socket/comm/socks.rb +237 -0
- data/lib/raptor-io/socket/comm_chain.rb +30 -0
- data/lib/raptor-io/socket/error.rb +45 -0
- data/lib/raptor-io/socket/switch_board.rb +183 -0
- data/lib/raptor-io/socket/switch_board/route.rb +42 -0
- data/lib/raptor-io/socket/tcp.rb +231 -0
- data/lib/raptor-io/socket/tcp/ssl.rb +77 -0
- data/lib/raptor-io/socket/tcp_server.rb +16 -0
- data/lib/raptor-io/socket/tcp_server/ssl.rb +52 -0
- data/lib/raptor-io/socket/udp.rb +0 -0
- data/lib/raptor-io/version.rb +6 -0
- data/lib/tasks/yard.rake +26 -0
- data/spec/rack/handler/raptor_spec.rb +140 -0
- data/spec/raptor-io/protocol/http/client_spec.rb +671 -0
- data/spec/raptor-io/protocol/http/headers_spec.rb +189 -0
- data/spec/raptor-io/protocol/http/message_spec.rb +5 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticator_spec.rb +193 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/basic_spec.rb +32 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/digest_spec.rb +76 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/negotiate_spec.rb +52 -0
- data/spec/raptor-io/protocol/http/request/manipulators/authenticators/ntlm_spec.rb +37 -0
- data/spec/raptor-io/protocol/http/request/manipulators/redirect_follower_spec.rb +51 -0
- data/spec/raptor-io/protocol/http/request/manipulators_spec.rb +202 -0
- data/spec/raptor-io/protocol/http/request_spec.rb +965 -0
- data/spec/raptor-io/protocol/http/response_spec.rb +236 -0
- data/spec/raptor-io/protocol/http/server_spec.rb +345 -0
- data/spec/raptor-io/ruby/hash_spec.rb +20 -0
- data/spec/raptor-io/ruby/string_spec.rb +20 -0
- data/spec/raptor-io/socket/comm/local_spec.rb +50 -0
- data/spec/raptor-io/socket/switch_board/route_spec.rb +49 -0
- data/spec/raptor-io/socket/switch_board_spec.rb +87 -0
- data/spec/raptor-io/socket/tcp/ssl_spec.rb +18 -0
- data/spec/raptor-io/socket/tcp_server/ssl_spec.rb +59 -0
- data/spec/raptor-io/socket/tcp_server_spec.rb +19 -0
- data/spec/raptor-io/socket/tcp_spec.rb +14 -0
- data/spec/raptor-io/socket_spec.rb +16 -0
- data/spec/raptor-io/version_spec.rb +10 -0
- data/spec/spec_helper.rb +56 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/manifoolators/fooer.rb +25 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/niccolo_machiavelli.rb +20 -0
- data/spec/support/fixtures/raptor/protocol/http/request/manipulators/options_validator.rb +28 -0
- data/spec/support/fixtures/raptor/socket/ssl_server.crt +18 -0
- data/spec/support/fixtures/raptor/socket/ssl_server.key +15 -0
- data/spec/support/lib/path_helpers.rb +11 -0
- data/spec/support/lib/webserver_option_parser.rb +26 -0
- data/spec/support/lib/webservers.rb +120 -0
- data/spec/support/shared/contexts/with_ssl_server.rb +70 -0
- data/spec/support/shared/contexts/with_tcp_server.rb +58 -0
- data/spec/support/shared/examples/raptor/comm_examples.rb +26 -0
- data/spec/support/shared/examples/raptor/protocols/http/message.rb +106 -0
- data/spec/support/shared/examples/raptor/socket_examples.rb +135 -0
- data/spec/support/webservers/raptor/protocols/http/client.rb +100 -0
- data/spec/support/webservers/raptor/protocols/http/client_close_connection.rb +29 -0
- data/spec/support/webservers/raptor/protocols/http/client_https.rb +43 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/basic.rb +9 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/authenticators/digest.rb +22 -0
- data/spec/support/webservers/raptor/protocols/http/request/manipulators/redirect_follower.rb +11 -0
- metadata +336 -0
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
require 'digest'
|
|
2
|
+
|
|
3
|
+
module RaptorIO
|
|
4
|
+
module Protocol::HTTP
|
|
5
|
+
class Request
|
|
6
|
+
|
|
7
|
+
module Manipulators
|
|
8
|
+
module Authenticators
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Implements HTTP Digest authentication as per RFC2069.
|
|
12
|
+
#
|
|
13
|
+
# @see http://tools.ietf.org/html/rfc2069
|
|
14
|
+
# @see http://en.wikipedia.org/wiki/Digest_access_authentication
|
|
15
|
+
#
|
|
16
|
+
# @author Tasos Laskos
|
|
17
|
+
#
|
|
18
|
+
class Digest < Manipulator
|
|
19
|
+
|
|
20
|
+
def run
|
|
21
|
+
request.headers['Authorization'] = {
|
|
22
|
+
'Digest username' => username,
|
|
23
|
+
realm: challenge[:realm],
|
|
24
|
+
nonce: challenge[:nonce],
|
|
25
|
+
uri: request.resource,
|
|
26
|
+
qop: challenge[:qop],
|
|
27
|
+
nc: nc,
|
|
28
|
+
cnonce: cnonce,
|
|
29
|
+
response: response,
|
|
30
|
+
algorithm: algorithm_name,
|
|
31
|
+
opaque: challenge[:opaque]
|
|
32
|
+
}.map { |k, v| "#{k}=\"#{v}\"" }.join( ', ' )
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def algorithm_klass
|
|
38
|
+
if challenge[:algorithm].to_s =~ /(.+)(-sess)?$/
|
|
39
|
+
case $1
|
|
40
|
+
when 'MD5' then ::Digest::MD5
|
|
41
|
+
when 'SHA1' then ::Digest::SHA1
|
|
42
|
+
when 'SHA2' then ::Digest::SHA2
|
|
43
|
+
when 'SHA256' then ::Digest::SHA256
|
|
44
|
+
when 'SHA384' then ::Digest::SHA384
|
|
45
|
+
when 'SHA512' then ::Digest::SHA512
|
|
46
|
+
when 'RMD160' then ::Digest::RMD160
|
|
47
|
+
else raise Error, "Unknown algorithm \"#{$1}\"."
|
|
48
|
+
end
|
|
49
|
+
else
|
|
50
|
+
::Digest::MD5
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def algorithm_name
|
|
55
|
+
algorithm_klass.to_s.split( '::' ).last
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def sess?
|
|
59
|
+
challenge[:algorithm].to_s.include? '-sess'
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def H( data )
|
|
63
|
+
algorithm_klass.hexdigest( data )
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def A1
|
|
67
|
+
without_sess = [ username, challenge[:realm], password ] * ':'
|
|
68
|
+
|
|
69
|
+
if sess?
|
|
70
|
+
H( [without_sess, challenge[:nonce], cnonce ] * ':' )
|
|
71
|
+
else
|
|
72
|
+
without_sess
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def A2
|
|
77
|
+
[ request.http_method.to_s.upcase, request.resource ] * ':'
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def H1
|
|
81
|
+
H( A1() )
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def H2
|
|
85
|
+
H( A2() )
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def response
|
|
89
|
+
if ['auth', 'auth-int'].include? challenge[:qop]
|
|
90
|
+
return H( [H1(), challenge[:nonce], nc, cnonce, challenge[:qop], H2()] * ':' )
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
H( [H1(), challenge[:nonce], H2()] * ':' )
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def cnonce
|
|
97
|
+
[Time.now.to_i.to_s].pack( 'm*' ).strip
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def nc
|
|
101
|
+
@nc ||= self.class.nc
|
|
102
|
+
end
|
|
103
|
+
def self.nc
|
|
104
|
+
@nc ||= 0
|
|
105
|
+
@nc += 1
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def challenge
|
|
109
|
+
return @challenge if @challenge
|
|
110
|
+
|
|
111
|
+
challenge_options = {}
|
|
112
|
+
options[:response].headers['www-authenticate'].split( ',' ).each do |pair|
|
|
113
|
+
matches = pair.strip.match( /(.+)="(.*)"/ )
|
|
114
|
+
challenge_options[matches[1].to_sym] = matches[2]
|
|
115
|
+
end
|
|
116
|
+
challenge_options[:realm] = challenge_options.delete( :'Digest realm' )
|
|
117
|
+
|
|
118
|
+
@challenge = challenge_options
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def username
|
|
122
|
+
options[:username]
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def password
|
|
126
|
+
options[:password]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
require 'net/ntlm'
|
|
2
|
+
|
|
3
|
+
module RaptorIO
|
|
4
|
+
module Protocol::HTTP
|
|
5
|
+
class Request
|
|
6
|
+
|
|
7
|
+
module Manipulators
|
|
8
|
+
module Authenticators
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Implements HTTP Negotiate authentication.
|
|
12
|
+
#
|
|
13
|
+
# @author Tasos Laskos
|
|
14
|
+
#
|
|
15
|
+
class Negotiate < Manipulator
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
return if skip?
|
|
19
|
+
client.manipulators.delete shortname
|
|
20
|
+
|
|
21
|
+
t2 = authorize( type1 ).headers['www-authenticate'].split( ' ' ).last
|
|
22
|
+
|
|
23
|
+
if authorize( type3( t2 ) ).code == 401 && client.manipulators['authenticator']
|
|
24
|
+
client.datastore['authenticator'][:failed] = true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def provider
|
|
31
|
+
'Negotiate'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def authorize( message )
|
|
35
|
+
client.get( request.url,
|
|
36
|
+
mode: :sync,
|
|
37
|
+
manipulators: {
|
|
38
|
+
'authenticator' => { skip: true },
|
|
39
|
+
shortname => { skip: true },
|
|
40
|
+
},
|
|
41
|
+
headers: { 'Authorization' => "#{provider} #{message}" } )
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def skip?
|
|
45
|
+
!!options[:skip]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def type1
|
|
49
|
+
Net::NTLM::Message::Type1.new.encode64
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def type3( type2 )
|
|
53
|
+
Net::NTLM::Message.decode64( type2 ).response(
|
|
54
|
+
{
|
|
55
|
+
user: options[:username],
|
|
56
|
+
password: options[:password],
|
|
57
|
+
domain: options[:domain]
|
|
58
|
+
},
|
|
59
|
+
{ ntlmv2: true }
|
|
60
|
+
).encode64
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
module RaptorIO
|
|
2
|
+
module Protocol::HTTP
|
|
3
|
+
class Request
|
|
4
|
+
|
|
5
|
+
module Manipulators
|
|
6
|
+
module Authenticators
|
|
7
|
+
|
|
8
|
+
if !const_defined?( :Negotiate )
|
|
9
|
+
load File.dirname( __FILE__ ) + '/negotiate.rb'
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Implements HTTP NTLM authentication.
|
|
14
|
+
#
|
|
15
|
+
# @author Tasos Laskos
|
|
16
|
+
#
|
|
17
|
+
class NTLM < Negotiate
|
|
18
|
+
|
|
19
|
+
def provider
|
|
20
|
+
'NTLM'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module RaptorIO
|
|
2
|
+
module Protocol::HTTP
|
|
3
|
+
class Request
|
|
4
|
+
|
|
5
|
+
module Manipulators
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# Implements automatic HTTP redirect following.
|
|
9
|
+
#
|
|
10
|
+
# @author Tasos Laskos
|
|
11
|
+
#
|
|
12
|
+
class RedirectFollower < Manipulator
|
|
13
|
+
|
|
14
|
+
def run
|
|
15
|
+
# This request has already been handled.
|
|
16
|
+
return if request.root_redirect_id
|
|
17
|
+
|
|
18
|
+
callbacks = request.callbacks.dup
|
|
19
|
+
request.clear_callbacks
|
|
20
|
+
|
|
21
|
+
request.on_complete do |response|
|
|
22
|
+
root_redirect_id = request.root_redirect_id ?
|
|
23
|
+
request.root_redirect_id : request.object_id
|
|
24
|
+
|
|
25
|
+
if response.redirect?
|
|
26
|
+
if redirections[root_redirect_id].size < max
|
|
27
|
+
redirections[root_redirect_id] << response
|
|
28
|
+
|
|
29
|
+
crequest = request.dup
|
|
30
|
+
crequest.root_redirect_id = root_redirect_id
|
|
31
|
+
|
|
32
|
+
# RFC says the Location URI must be a full absolute URL however not
|
|
33
|
+
# all webapps respect that.
|
|
34
|
+
crequest.url = crequest.parsed_url.merge( response.headers['Location'] ).to_s
|
|
35
|
+
|
|
36
|
+
client.queue( crequest )
|
|
37
|
+
next
|
|
38
|
+
else
|
|
39
|
+
response.redirections = redirections.delete( root_redirect_id )
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
request.callbacks = callbacks
|
|
44
|
+
request.handle_response response
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
# @return [Hash<Integer, Array<RaptorIO::Protocol::HTTP::Response>]
|
|
51
|
+
# Keeps track of stacked redirections based on the ID of their root request.
|
|
52
|
+
def redirections
|
|
53
|
+
datastore[:redirections] ||= Hash.new { |h, k| h[k] = [] }
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def max
|
|
57
|
+
@max ||= (options[:max] || 5).to_i
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
require 'zlib'
|
|
2
|
+
require 'stringio'
|
|
3
|
+
|
|
4
|
+
module RaptorIO
|
|
5
|
+
module Protocol::HTTP
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# HTTP Response.
|
|
9
|
+
#
|
|
10
|
+
# @author Tasos Laskos <tasos_laskos@rapid7.com>
|
|
11
|
+
#
|
|
12
|
+
class Response < Message
|
|
13
|
+
|
|
14
|
+
# @return [Integer] HTTP response status code.
|
|
15
|
+
attr_accessor :code
|
|
16
|
+
|
|
17
|
+
# @return [String] HTTP response status message.
|
|
18
|
+
attr_accessor :message
|
|
19
|
+
|
|
20
|
+
# @return [Request] HTTP {Request} which triggered this {Response}.
|
|
21
|
+
attr_accessor :request
|
|
22
|
+
|
|
23
|
+
# @return [Array<Response>]
|
|
24
|
+
# Automatically followed redirections that eventually led to this response.
|
|
25
|
+
attr_accessor :redirections
|
|
26
|
+
|
|
27
|
+
# @return [Exception] Exception representing the error that occurred.
|
|
28
|
+
attr_accessor :error
|
|
29
|
+
|
|
30
|
+
#
|
|
31
|
+
# @note This class' options are in addition to {Message#initialize}.
|
|
32
|
+
#
|
|
33
|
+
# @param [Hash] options Request options.
|
|
34
|
+
# @option options [Integer] :code HTTP response status code.
|
|
35
|
+
# @option options [Request] :request HTTP request that triggered this response.
|
|
36
|
+
#
|
|
37
|
+
# @see Message#initialize
|
|
38
|
+
#
|
|
39
|
+
def initialize( options = {} )
|
|
40
|
+
super( options )
|
|
41
|
+
|
|
42
|
+
@body = @body.force_utf8 if text?
|
|
43
|
+
@code ||= 0
|
|
44
|
+
|
|
45
|
+
# Holds the redirection responses that eventually led to this one.
|
|
46
|
+
@redirections ||= []
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @return [Boolean]
|
|
50
|
+
# `true` if the response is a `3xx` redirect **and** there is a `Location`
|
|
51
|
+
# header field.
|
|
52
|
+
def redirect?
|
|
53
|
+
code >= 300 && code <= 399 && !!headers['Location']
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @note Depends on the response code.
|
|
57
|
+
#
|
|
58
|
+
# @return [Boolean]
|
|
59
|
+
# `true` if the remote resource has been modified since the date given in
|
|
60
|
+
# the `If-Modified-Since` request header field, `false` otherwise.
|
|
61
|
+
def modified?
|
|
62
|
+
code != 304
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# @return [Bool]
|
|
66
|
+
# `true` if the response body is textual in nature, `false` otherwise
|
|
67
|
+
# (if binary).
|
|
68
|
+
def text?
|
|
69
|
+
return if !@body
|
|
70
|
+
|
|
71
|
+
if (type = headers['content-type'])
|
|
72
|
+
return true if type.start_with?( 'text/' )
|
|
73
|
+
|
|
74
|
+
# Non "application/" content types will surely not be text-based
|
|
75
|
+
# so bail out early.
|
|
76
|
+
return false if !type.start_with?( 'application/' )
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Last resort, more resource intensive binary detection.
|
|
80
|
+
!@body.binary?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# @return [String]
|
|
84
|
+
# String representation of the response.
|
|
85
|
+
def to_s
|
|
86
|
+
headers['Content-Length'] = body.to_s.size
|
|
87
|
+
|
|
88
|
+
r = "HTTP/#{version} #{code}"
|
|
89
|
+
r << " #{message}" if message
|
|
90
|
+
r << "\r\n"
|
|
91
|
+
r << "#{headers.to_s}\r\n\r\n"
|
|
92
|
+
r << body.to_s
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @param [String] response HTTP response.
|
|
96
|
+
# @return [Response]
|
|
97
|
+
def self.parse( response )
|
|
98
|
+
options ||= {}
|
|
99
|
+
|
|
100
|
+
# FIXME: The existence of this extra newline at the beginning of a
|
|
101
|
+
# response suggests a bug somewhere else in the response parsing
|
|
102
|
+
# code.
|
|
103
|
+
response = response.gsub(/\A\r\n/, '')
|
|
104
|
+
|
|
105
|
+
headers_string, options[:body] = response.split( HEADER_SEPARATOR_PATTERN, 2 )
|
|
106
|
+
request_line = headers_string.to_s.lines.first.to_s.chomp
|
|
107
|
+
|
|
108
|
+
options[:version], options[:code], options[:message] =
|
|
109
|
+
request_line.scan( /HTTP\/([\d.]+)\s+(\d+)\s*(.*)\s*$/ ).flatten
|
|
110
|
+
|
|
111
|
+
options.delete(:message) if options[:message].to_s.empty?
|
|
112
|
+
|
|
113
|
+
options[:code] = options[:code].to_i
|
|
114
|
+
|
|
115
|
+
if !headers_string.to_s.empty?
|
|
116
|
+
options[:headers] =
|
|
117
|
+
Headers.parse( headers_string.split( CRLF_PATTERN )[1..-1].join( "\r\n" ) )
|
|
118
|
+
else
|
|
119
|
+
options[:headers] = Headers.new
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
if !options[:body].to_s.empty?
|
|
123
|
+
|
|
124
|
+
# If any encoding has been applied to the body, remove all evidence of it
|
|
125
|
+
# and adjust the content-length accordingly.
|
|
126
|
+
|
|
127
|
+
case options[:headers]['content-encoding'].to_s.downcase
|
|
128
|
+
when 'gzip', 'x-gzip'
|
|
129
|
+
options[:body] = unzip( options[:body] )
|
|
130
|
+
when 'deflate', 'compress', 'x-compress'
|
|
131
|
+
options[:body] = inflate( options[:body] )
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
if options[:headers].delete( 'content-encoding' ) ||
|
|
135
|
+
options[:headers].delete( 'transfer-encoding' )
|
|
136
|
+
options[:headers]['content-length'] = options[:body].size
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
new( options )
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# @param [String] str Inflates `str`.
|
|
144
|
+
# @return [String] Inflated `str`.
|
|
145
|
+
def self.inflate( str )
|
|
146
|
+
z = Zlib::Inflate.new
|
|
147
|
+
s = z.inflate( str )
|
|
148
|
+
z.close
|
|
149
|
+
s
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# @param [String] str Unzips `str`.
|
|
153
|
+
# @return [String] Unziped `str`.
|
|
154
|
+
def self.unzip( str )
|
|
155
|
+
s = ''
|
|
156
|
+
s.force_encoding( 'ASCII-8BIT' ) if s.respond_to?( :encoding )
|
|
157
|
+
gz = Zlib::GzipReader.new( StringIO.new( str, 'rb' ) )
|
|
158
|
+
s << gz.read
|
|
159
|
+
gz.close
|
|
160
|
+
s
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
end
|
|
166
|
+
end
|