framed_rails 0.1.0
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.
- data/.gitignore +11 -0
- data/.ruby-version +1 -0
- data/CHANGELOG +1 -0
- data/Gemfile +3 -0
- data/LICENSE +1 -0
- data/README.md +107 -0
- data/framed_rails.gemspec +37 -0
- data/lib/framed/client.rb +34 -0
- data/lib/framed/emitters.rb +113 -0
- data/lib/framed/example.rb +17 -0
- data/lib/framed/exceptions.rb +13 -0
- data/lib/framed/okjson.rb +602 -0
- data/lib/framed/rails.rb +43 -0
- data/lib/framed/railtie.rb +9 -0
- data/lib/framed/utils.rb +54 -0
- data/lib/framed/version.rb +4 -0
- data/lib/framed_rails.rb +71 -0
- data/vendor/gems/excon-0.45.3/data/cacert.pem +3860 -0
- data/vendor/gems/excon-0.45.3/lib/excon/connection.rb +469 -0
- data/vendor/gems/excon-0.45.3/lib/excon/constants.rb +142 -0
- data/vendor/gems/excon-0.45.3/lib/excon/errors.rb +155 -0
- data/vendor/gems/excon-0.45.3/lib/excon/extensions/uri.rb +33 -0
- data/vendor/gems/excon-0.45.3/lib/excon/headers.rb +83 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/base.rb +24 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/decompress.rb +35 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/escape_path.rb +11 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/expects.rb +18 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/idempotent.rb +33 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/instrumentor.rb +34 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/mock.rb +51 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/redirect_follower.rb +56 -0
- data/vendor/gems/excon-0.45.3/lib/excon/middlewares/response_parser.rb +12 -0
- data/vendor/gems/excon-0.45.3/lib/excon/pretty_printer.rb +45 -0
- data/vendor/gems/excon-0.45.3/lib/excon/response.rb +212 -0
- data/vendor/gems/excon-0.45.3/lib/excon/socket.rb +310 -0
- data/vendor/gems/excon-0.45.3/lib/excon/ssl_socket.rb +151 -0
- data/vendor/gems/excon-0.45.3/lib/excon/standard_instrumentor.rb +27 -0
- data/vendor/gems/excon-0.45.3/lib/excon/unix_socket.rb +40 -0
- data/vendor/gems/excon-0.45.3/lib/excon/utils.rb +87 -0
- data/vendor/gems/excon-0.45.3/lib/excon.rb +234 -0
- metadata +91 -0
@@ -0,0 +1,212 @@
|
|
1
|
+
module Excon
|
2
|
+
class Response
|
3
|
+
|
4
|
+
attr_accessor :data
|
5
|
+
|
6
|
+
# backwards compatability reader/writers
|
7
|
+
def body=(new_body)
|
8
|
+
@data[:body] = new_body
|
9
|
+
end
|
10
|
+
def body
|
11
|
+
@data[:body]
|
12
|
+
end
|
13
|
+
def headers=(new_headers)
|
14
|
+
@data[:headers] = new_headers
|
15
|
+
end
|
16
|
+
def headers
|
17
|
+
@data[:headers]
|
18
|
+
end
|
19
|
+
def status=(new_status)
|
20
|
+
@data[:status] = new_status
|
21
|
+
end
|
22
|
+
def status
|
23
|
+
@data[:status]
|
24
|
+
end
|
25
|
+
def status_line
|
26
|
+
@data[:status_line]
|
27
|
+
end
|
28
|
+
def status_line=(new_status_line)
|
29
|
+
@data[:status_line] = new_status_line
|
30
|
+
end
|
31
|
+
def reason_phrase=(new_reason_phrase)
|
32
|
+
@data[:reason_phrase] = new_reason_phrase
|
33
|
+
end
|
34
|
+
def reason_phrase
|
35
|
+
@data[:reason_phrase]
|
36
|
+
end
|
37
|
+
def remote_ip=(new_remote_ip)
|
38
|
+
@data[:remote_ip] = new_remote_ip
|
39
|
+
end
|
40
|
+
def remote_ip
|
41
|
+
@data[:remote_ip]
|
42
|
+
end
|
43
|
+
def local_port
|
44
|
+
@data[:local_port]
|
45
|
+
end
|
46
|
+
def local_address
|
47
|
+
@data[:local_address]
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.parse(socket, datum)
|
51
|
+
# this will discard any trailing lines from the previous response if any.
|
52
|
+
begin
|
53
|
+
line = socket.readline
|
54
|
+
end until status = line[9, 3].to_i
|
55
|
+
|
56
|
+
reason_phrase = line[13..-3] # -3 strips the trailing "\r\n"
|
57
|
+
|
58
|
+
datum[:response] = {
|
59
|
+
:body => '',
|
60
|
+
:headers => Excon::Headers.new,
|
61
|
+
:status => status,
|
62
|
+
:status_line => line,
|
63
|
+
:reason_phrase => reason_phrase
|
64
|
+
}
|
65
|
+
|
66
|
+
unix_proxy = datum[:proxy] ? datum[:proxy][:scheme] == UNIX : false
|
67
|
+
unless datum[:scheme] == UNIX || unix_proxy
|
68
|
+
datum[:response].merge!(
|
69
|
+
:remote_ip => socket.remote_ip,
|
70
|
+
:local_port => socket.local_port,
|
71
|
+
:local_address => socket.local_address
|
72
|
+
)
|
73
|
+
end
|
74
|
+
|
75
|
+
parse_headers(socket, datum)
|
76
|
+
|
77
|
+
unless (['HEAD', 'CONNECT'].include?(datum[:method].to_s.upcase)) || NO_ENTITY.include?(datum[:response][:status])
|
78
|
+
|
79
|
+
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Transfer-Encoding') == 0 }
|
80
|
+
encodings = Utils.split_header_value(datum[:response][:headers][key])
|
81
|
+
if (encoding = encodings.last) && encoding.casecmp('chunked') == 0
|
82
|
+
transfer_encoding_chunked = true
|
83
|
+
if encodings.length == 1
|
84
|
+
datum[:response][:headers].delete(key)
|
85
|
+
else
|
86
|
+
datum[:response][:headers][key] = encodings[0...-1].join(', ')
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
# use :response_block unless :expects would fail
|
92
|
+
if response_block = datum[:response_block]
|
93
|
+
if datum[:middlewares].include?(Excon::Middleware::Expects) && datum[:expects] &&
|
94
|
+
!Array(datum[:expects]).include?(datum[:response][:status])
|
95
|
+
response_block = nil
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
if transfer_encoding_chunked
|
100
|
+
if response_block
|
101
|
+
while (chunk_size = socket.readline.chomp!.to_i(16)) > 0
|
102
|
+
while chunk_size > 0
|
103
|
+
chunk = socket.read(chunk_size)
|
104
|
+
chunk_size -= chunk.bytesize
|
105
|
+
response_block.call(chunk, nil, nil)
|
106
|
+
end
|
107
|
+
new_line_size = 2 # 2 == "\r\n".length
|
108
|
+
while new_line_size > 0
|
109
|
+
new_line_size -= socket.read(new_line_size).length
|
110
|
+
end
|
111
|
+
end
|
112
|
+
else
|
113
|
+
while (chunk_size = socket.readline.chomp!.to_i(16)) > 0
|
114
|
+
while chunk_size > 0
|
115
|
+
chunk = socket.read(chunk_size)
|
116
|
+
chunk_size -= chunk.bytesize
|
117
|
+
datum[:response][:body] << chunk
|
118
|
+
end
|
119
|
+
new_line_size = 2 # 2 == "\r\n".length
|
120
|
+
while new_line_size > 0
|
121
|
+
new_line_size -= socket.read(new_line_size).length
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
parse_headers(socket, datum) # merge trailers into headers
|
126
|
+
else
|
127
|
+
if key = datum[:response][:headers].keys.detect {|k| k.casecmp('Content-Length') == 0 }
|
128
|
+
content_length = datum[:response][:headers][key].to_i
|
129
|
+
end
|
130
|
+
|
131
|
+
if remaining = content_length
|
132
|
+
if response_block
|
133
|
+
while remaining > 0
|
134
|
+
chunk = socket.read([datum[:chunk_size], remaining].min)
|
135
|
+
response_block.call(chunk, [remaining - chunk.bytesize, 0].max, content_length)
|
136
|
+
remaining -= chunk.bytesize
|
137
|
+
end
|
138
|
+
else
|
139
|
+
while remaining > 0
|
140
|
+
chunk = socket.read([datum[:chunk_size], remaining].min)
|
141
|
+
datum[:response][:body] << chunk
|
142
|
+
remaining -= chunk.bytesize
|
143
|
+
end
|
144
|
+
end
|
145
|
+
else
|
146
|
+
if response_block
|
147
|
+
while chunk = socket.read(datum[:chunk_size])
|
148
|
+
response_block.call(chunk, nil, nil)
|
149
|
+
end
|
150
|
+
else
|
151
|
+
while chunk = socket.read(datum[:chunk_size])
|
152
|
+
datum[:response][:body] << chunk
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
datum
|
159
|
+
end
|
160
|
+
|
161
|
+
def self.parse_headers(socket, datum)
|
162
|
+
last_key = nil
|
163
|
+
until (data = socket.readline.chomp!).empty?
|
164
|
+
if !data.lstrip!.nil?
|
165
|
+
raise Excon::Errors::ResponseParseError, 'malformed header' unless last_key
|
166
|
+
# append to last_key's last value
|
167
|
+
datum[:response][:headers][last_key] << ' ' << data.rstrip
|
168
|
+
else
|
169
|
+
key, value = data.split(':', 2)
|
170
|
+
raise Excon::Errors::ResponseParseError, 'malformed header' unless value
|
171
|
+
# add key/value or append value to existing values
|
172
|
+
datum[:response][:headers][key] = ([datum[:response][:headers][key]] << value.strip).compact.join(', ')
|
173
|
+
last_key = key
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
def initialize(params={})
|
179
|
+
@data = {
|
180
|
+
:body => ''
|
181
|
+
}.merge(params)
|
182
|
+
@data[:headers] = Excon::Headers.new.merge!(params[:headers] || {})
|
183
|
+
|
184
|
+
@body = @data[:body]
|
185
|
+
@headers = @data[:headers]
|
186
|
+
@status = @data[:status]
|
187
|
+
@remote_ip = @data[:remote_ip]
|
188
|
+
@local_port = @data[:local_port]
|
189
|
+
@local_address = @data[:local_address]
|
190
|
+
end
|
191
|
+
|
192
|
+
def [](key)
|
193
|
+
@data[key]
|
194
|
+
end
|
195
|
+
|
196
|
+
def params
|
197
|
+
Excon.display_warning('Excon::Response#params is deprecated use Excon::Response#data instead.')
|
198
|
+
data
|
199
|
+
end
|
200
|
+
|
201
|
+
def pp
|
202
|
+
Excon::PrettyPrinter.pp($stdout, @data)
|
203
|
+
end
|
204
|
+
|
205
|
+
# Retrieve a specific header value. Header names are treated case-insensitively.
|
206
|
+
# @param [String] name Header name
|
207
|
+
def get_header(name)
|
208
|
+
headers[name]
|
209
|
+
end
|
210
|
+
|
211
|
+
end # class Response
|
212
|
+
end # module Excon
|
@@ -0,0 +1,310 @@
|
|
1
|
+
module Excon
|
2
|
+
class Socket
|
3
|
+
include Utils
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_accessor :data
|
8
|
+
|
9
|
+
def params
|
10
|
+
Excon.display_warning('Excon::Socket#params is deprecated use Excon::Socket#data instead.')
|
11
|
+
@data
|
12
|
+
end
|
13
|
+
|
14
|
+
def params=(new_params)
|
15
|
+
Excon.display_warning('Excon::Socket#params= is deprecated use Excon::Socket#data= instead.')
|
16
|
+
@data = new_params
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :remote_ip
|
20
|
+
|
21
|
+
def_delegators(:@socket, :close)
|
22
|
+
|
23
|
+
def initialize(data = {})
|
24
|
+
@data = data
|
25
|
+
@nonblock = data[:nonblock]
|
26
|
+
@read_buffer = ''
|
27
|
+
@eof = false
|
28
|
+
connect
|
29
|
+
end
|
30
|
+
|
31
|
+
def read(max_length = nil)
|
32
|
+
if @eof
|
33
|
+
return max_length ? nil : ''
|
34
|
+
elsif @nonblock
|
35
|
+
read_nonblock(max_length)
|
36
|
+
else
|
37
|
+
read_block(max_length)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def readline
|
42
|
+
return legacy_readline if RUBY_VERSION.to_f <= 1.8_7
|
43
|
+
buffer = ''
|
44
|
+
begin
|
45
|
+
buffer << @socket.read_nonblock(1) while buffer[-1] != "\n"
|
46
|
+
buffer
|
47
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
48
|
+
if timeout_reached('read')
|
49
|
+
raise_timeout_error('read')
|
50
|
+
else
|
51
|
+
retry
|
52
|
+
end
|
53
|
+
rescue OpenSSL::SSL::SSLError => e
|
54
|
+
if e.message == 'read would block'
|
55
|
+
if timeout_reached('read')
|
56
|
+
raise_timeout_error('read')
|
57
|
+
else
|
58
|
+
retry
|
59
|
+
end
|
60
|
+
else
|
61
|
+
raise(error)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def legacy_readline
|
67
|
+
begin
|
68
|
+
Timeout.timeout(@data[:read_timeout]) do
|
69
|
+
@socket.readline
|
70
|
+
end
|
71
|
+
rescue Timeout::Error
|
72
|
+
raise Excon::Errors::Timeout.new('read timeout reached')
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def write(data)
|
77
|
+
if @nonblock
|
78
|
+
write_nonblock(data)
|
79
|
+
else
|
80
|
+
write_block(data)
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def local_address
|
85
|
+
unpacked_sockaddr[1]
|
86
|
+
end
|
87
|
+
|
88
|
+
def local_port
|
89
|
+
unpacked_sockaddr[0]
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def connect
|
95
|
+
@socket = nil
|
96
|
+
exception = nil
|
97
|
+
|
98
|
+
if @data[:proxy]
|
99
|
+
family = @data[:proxy][:family] || ::Socket::Constants::AF_UNSPEC
|
100
|
+
args = [@data[:proxy][:hostname], @data[:proxy][:port], family, ::Socket::Constants::SOCK_STREAM]
|
101
|
+
else
|
102
|
+
family = @data[:family] || ::Socket::Constants::AF_UNSPEC
|
103
|
+
args = [@data[:hostname], @data[:port], family, ::Socket::Constants::SOCK_STREAM]
|
104
|
+
end
|
105
|
+
if RUBY_VERSION >= '1.9.2' && defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby'
|
106
|
+
args << nil << nil << false # no reverse lookup
|
107
|
+
end
|
108
|
+
addrinfo = ::Socket.getaddrinfo(*args)
|
109
|
+
|
110
|
+
addrinfo.each do |_, port, _, ip, a_family, s_type|
|
111
|
+
@remote_ip = ip
|
112
|
+
|
113
|
+
# already succeeded on previous addrinfo
|
114
|
+
if @socket
|
115
|
+
break
|
116
|
+
end
|
117
|
+
|
118
|
+
# nonblocking connect
|
119
|
+
begin
|
120
|
+
sockaddr = ::Socket.sockaddr_in(port, ip)
|
121
|
+
|
122
|
+
socket = ::Socket.new(a_family, s_type, 0)
|
123
|
+
|
124
|
+
if @data[:reuseaddr]
|
125
|
+
socket.setsockopt(::Socket::Constants::SOL_SOCKET, ::Socket::Constants::SO_REUSEADDR, true)
|
126
|
+
if defined?(::Socket::Constants::SO_REUSEPORT)
|
127
|
+
socket.setsockopt(::Socket::Constants::SOL_SOCKET, ::Socket::Constants::SO_REUSEPORT, true)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
|
131
|
+
if @nonblock
|
132
|
+
socket.connect_nonblock(sockaddr)
|
133
|
+
else
|
134
|
+
socket.connect(sockaddr)
|
135
|
+
end
|
136
|
+
@socket = socket
|
137
|
+
rescue Errno::EINPROGRESS
|
138
|
+
unless IO.select(nil, [socket], nil, @data[:connect_timeout])
|
139
|
+
raise(Excon::Errors::Timeout.new('connect timeout reached'))
|
140
|
+
end
|
141
|
+
begin
|
142
|
+
socket.connect_nonblock(sockaddr)
|
143
|
+
@socket = socket
|
144
|
+
rescue Errno::EISCONN
|
145
|
+
@socket = socket
|
146
|
+
rescue SystemCallError => exception
|
147
|
+
socket.close rescue nil
|
148
|
+
end
|
149
|
+
rescue SystemCallError => exception
|
150
|
+
socket.close rescue nil if socket
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
# this will be our last encountered exception
|
155
|
+
fail exception unless @socket
|
156
|
+
|
157
|
+
if @data[:tcp_nodelay]
|
158
|
+
@socket.setsockopt(::Socket::IPPROTO_TCP,
|
159
|
+
::Socket::TCP_NODELAY,
|
160
|
+
true)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def read_nonblock(max_length)
|
165
|
+
begin
|
166
|
+
if max_length
|
167
|
+
until @read_buffer.length >= max_length
|
168
|
+
@read_buffer << @socket.read_nonblock(max_length - @read_buffer.length)
|
169
|
+
end
|
170
|
+
else
|
171
|
+
loop do
|
172
|
+
@read_buffer << @socket.read_nonblock(@data[:chunk_size])
|
173
|
+
end
|
174
|
+
end
|
175
|
+
rescue OpenSSL::SSL::SSLError => error
|
176
|
+
if error.message == 'read would block'
|
177
|
+
if timeout_reached('read')
|
178
|
+
raise_timeout_error('read')
|
179
|
+
else
|
180
|
+
retry
|
181
|
+
end
|
182
|
+
else
|
183
|
+
raise(error)
|
184
|
+
end
|
185
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
186
|
+
if @read_buffer.empty?
|
187
|
+
# if we didn't read anything, try again...
|
188
|
+
if timeout_reached('read')
|
189
|
+
raise_timeout_error('read')
|
190
|
+
else
|
191
|
+
retry
|
192
|
+
end
|
193
|
+
end
|
194
|
+
rescue EOFError
|
195
|
+
@eof = true
|
196
|
+
end
|
197
|
+
|
198
|
+
if max_length
|
199
|
+
if @read_buffer.empty?
|
200
|
+
nil # EOF met at beginning
|
201
|
+
else
|
202
|
+
@read_buffer.slice!(0, max_length)
|
203
|
+
end
|
204
|
+
else
|
205
|
+
# read until EOFError, so return everything
|
206
|
+
@read_buffer.slice!(0, @read_buffer.length)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def read_block(max_length)
|
211
|
+
@socket.read(max_length)
|
212
|
+
rescue OpenSSL::SSL::SSLError => error
|
213
|
+
if error.message == 'read would block'
|
214
|
+
if timeout_reached('read')
|
215
|
+
raise_timeout_error('read')
|
216
|
+
else
|
217
|
+
retry
|
218
|
+
end
|
219
|
+
else
|
220
|
+
raise(error)
|
221
|
+
end
|
222
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
223
|
+
if @read_buffer.empty?
|
224
|
+
if timeout_reached('read')
|
225
|
+
raise_timeout_error('read')
|
226
|
+
else
|
227
|
+
retry
|
228
|
+
end
|
229
|
+
end
|
230
|
+
rescue EOFError
|
231
|
+
@eof = true
|
232
|
+
end
|
233
|
+
|
234
|
+
def write_nonblock(data)
|
235
|
+
if FORCE_ENC
|
236
|
+
data.force_encoding('BINARY')
|
237
|
+
end
|
238
|
+
loop do
|
239
|
+
written = nil
|
240
|
+
begin
|
241
|
+
# I wish that this API accepted a start position, then we wouldn't
|
242
|
+
# have to slice data when there is a short write.
|
243
|
+
written = @socket.write_nonblock(data)
|
244
|
+
rescue Errno::EFAULT
|
245
|
+
if OpenSSL.const_defined?(:OPENSSL_LIBRARY_VERSION) && OpenSSL::OPENSSL_LIBRARY_VERSION.split(' ')[1] == '1.0.2'
|
246
|
+
msg = "The version of OpenSSL this ruby is built against (1.0.2) has a vulnerability
|
247
|
+
which causes a fault. For more, see https://github.com/excon/excon/issues/467"
|
248
|
+
raise SecurityError.new(msg)
|
249
|
+
else
|
250
|
+
raise error
|
251
|
+
end
|
252
|
+
rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
|
253
|
+
if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
|
254
|
+
raise error
|
255
|
+
else
|
256
|
+
if timeout_reached('write')
|
257
|
+
raise_timeout_error('write')
|
258
|
+
else
|
259
|
+
retry
|
260
|
+
end
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# Fast, common case.
|
265
|
+
break if written == data.size
|
266
|
+
|
267
|
+
# This takes advantage of the fact that most ruby implementations
|
268
|
+
# have Copy-On-Write strings. Thusly why requesting a subrange
|
269
|
+
# of data, we actually don't copy data because the new string
|
270
|
+
# simply references a subrange of the original.
|
271
|
+
data = data[written, data.size]
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
def write_block(data)
|
276
|
+
@socket.write(data)
|
277
|
+
rescue OpenSSL::SSL::SSLError, Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitWritable => error
|
278
|
+
if error.is_a?(OpenSSL::SSL::SSLError) && error.message != 'write would block'
|
279
|
+
raise error
|
280
|
+
else
|
281
|
+
if timeout_reached('write')
|
282
|
+
raise_timeout_error('write')
|
283
|
+
else
|
284
|
+
retry
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def timeout_reached(type)
|
290
|
+
if type == 'read'
|
291
|
+
args = [[@socket], nil, nil, @data[:read_timeout]]
|
292
|
+
else
|
293
|
+
args = [nil, [@socket], nil, @data[:write_timeout]]
|
294
|
+
end
|
295
|
+
IO.select(*args) ? nil : true
|
296
|
+
end
|
297
|
+
|
298
|
+
def raise_timeout_error(type)
|
299
|
+
fail Excon::Errors::Timeout.new("#{type} timeout reached")
|
300
|
+
end
|
301
|
+
|
302
|
+
def unpacked_sockaddr
|
303
|
+
@unpacked_sockaddr ||= ::Socket.unpack_sockaddr_in(@socket.to_io.getsockname)
|
304
|
+
rescue ArgumentError => e
|
305
|
+
unless e.message == 'not an AF_INET/AF_INET6 sockaddr'
|
306
|
+
raise
|
307
|
+
end
|
308
|
+
end
|
309
|
+
end
|
310
|
+
end
|
@@ -0,0 +1,151 @@
|
|
1
|
+
module Excon
|
2
|
+
class SSLSocket < Socket
|
3
|
+
HAVE_NONBLOCK = [:connect_nonblock, :read_nonblock, :write_nonblock].all? do |m|
|
4
|
+
OpenSSL::SSL::SSLSocket.public_method_defined?(m)
|
5
|
+
end
|
6
|
+
|
7
|
+
def initialize(data = {})
|
8
|
+
super
|
9
|
+
|
10
|
+
# create ssl context
|
11
|
+
ssl_context = OpenSSL::SSL::SSLContext.new
|
12
|
+
|
13
|
+
# disable less secure options, when supported
|
14
|
+
ssl_context_options = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:options]
|
15
|
+
if defined?(OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS)
|
16
|
+
ssl_context_options &= ~OpenSSL::SSL::OP_DONT_INSERT_EMPTY_FRAGMENTS
|
17
|
+
end
|
18
|
+
|
19
|
+
if defined?(OpenSSL::SSL::OP_NO_COMPRESSION)
|
20
|
+
ssl_context_options |= OpenSSL::SSL::OP_NO_COMPRESSION
|
21
|
+
end
|
22
|
+
ssl_context.options = ssl_context_options
|
23
|
+
|
24
|
+
ssl_context.ciphers = @data[:ciphers]
|
25
|
+
if @data[:ssl_version]
|
26
|
+
ssl_context.ssl_version = @data[:ssl_version]
|
27
|
+
end
|
28
|
+
|
29
|
+
if @data[:ssl_verify_peer]
|
30
|
+
# turn verification on
|
31
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
32
|
+
|
33
|
+
if ca_file = @data[:ssl_ca_file] || ENV['SSL_CERT_FILE']
|
34
|
+
ssl_context.ca_file = ca_file
|
35
|
+
end
|
36
|
+
if ca_path = @data[:ssl_ca_path] || ENV['SSL_CERT_DIR']
|
37
|
+
ssl_context.ca_path = ca_path
|
38
|
+
end
|
39
|
+
if cert_store = @data[:ssl_cert_store]
|
40
|
+
ssl_context.cert_store = cert_store
|
41
|
+
end
|
42
|
+
|
43
|
+
# no defaults, fallback to bundled
|
44
|
+
unless ca_file || ca_path || cert_store
|
45
|
+
ssl_context.cert_store = OpenSSL::X509::Store.new
|
46
|
+
ssl_context.cert_store.set_default_paths
|
47
|
+
|
48
|
+
# workaround issue #257 (JRUBY-6970)
|
49
|
+
ca_file = DEFAULT_CA_FILE
|
50
|
+
ca_file.gsub!(/^jar:/, '') if ca_file =~ /^jar:file:\//
|
51
|
+
|
52
|
+
begin
|
53
|
+
ssl_context.cert_store.add_file(ca_file)
|
54
|
+
rescue
|
55
|
+
Excon.display_warning("Excon unable to add file to cert store, ignoring: #{ca_file}\n[#{$!.class}] #{$!.message}")
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
if verify_callback = @data[:ssl_verify_callback]
|
60
|
+
ssl_context.verify_callback = verify_callback
|
61
|
+
end
|
62
|
+
else
|
63
|
+
# turn verification off
|
64
|
+
ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
65
|
+
end
|
66
|
+
|
67
|
+
# maintain existing API
|
68
|
+
certificate_path = @data[:client_cert] || @data[:certificate_path]
|
69
|
+
private_key_path = @data[:client_key] || @data[:private_key_path]
|
70
|
+
private_key_pass = @data[:client_key_pass] || @data[:private_key_pass]
|
71
|
+
|
72
|
+
if certificate_path && private_key_path
|
73
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new(File.read(certificate_path))
|
74
|
+
if OpenSSL::PKey.respond_to? :read
|
75
|
+
ssl_context.key = OpenSSL::PKey.read(File.read(private_key_path), private_key_pass)
|
76
|
+
else
|
77
|
+
ssl_context.key = OpenSSL::PKey::RSA.new(File.read(private_key_path), private_key_pass)
|
78
|
+
end
|
79
|
+
elsif @data.key?(:certificate) && @data.key?(:private_key)
|
80
|
+
ssl_context.cert = OpenSSL::X509::Certificate.new(@data[:certificate])
|
81
|
+
if OpenSSL::PKey.respond_to? :read
|
82
|
+
ssl_context.key = OpenSSL::PKey.read(@data[:private_key], private_key_pass)
|
83
|
+
else
|
84
|
+
ssl_context.key = OpenSSL::PKey::RSA.new(@data[:private_key], private_key_pass)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if @data[:proxy]
|
89
|
+
request = 'CONNECT ' << @data[:host] << port_string(@data.merge(:omit_default_port => false)) << Excon::HTTP_1_1
|
90
|
+
request << 'Host: ' << @data[:host] << port_string(@data) << Excon::CR_NL
|
91
|
+
|
92
|
+
if @data[:proxy][:password] || @data[:proxy][:user]
|
93
|
+
auth = ['' << @data[:proxy][:user].to_s << ':' << @data[:proxy][:password].to_s].pack('m').delete(Excon::CR_NL)
|
94
|
+
request << 'Proxy-Authorization: Basic ' << auth << Excon::CR_NL
|
95
|
+
end
|
96
|
+
|
97
|
+
request << 'Proxy-Connection: Keep-Alive' << Excon::CR_NL
|
98
|
+
|
99
|
+
request << Excon::CR_NL
|
100
|
+
|
101
|
+
# write out the proxy setup request
|
102
|
+
@socket.write(request)
|
103
|
+
|
104
|
+
# eat the proxy's connection response
|
105
|
+
Excon::Response.parse(self, :expects => 200, :method => 'CONNECT')
|
106
|
+
end
|
107
|
+
|
108
|
+
# convert Socket to OpenSSL::SSL::SSLSocket
|
109
|
+
@socket = OpenSSL::SSL::SSLSocket.new(@socket, ssl_context)
|
110
|
+
@socket.sync_close = true
|
111
|
+
|
112
|
+
# Server Name Indication (SNI) RFC 3546
|
113
|
+
if @socket.respond_to?(:hostname=)
|
114
|
+
@socket.hostname = @data[:host]
|
115
|
+
end
|
116
|
+
|
117
|
+
begin
|
118
|
+
if @nonblock
|
119
|
+
begin
|
120
|
+
@socket.connect_nonblock
|
121
|
+
rescue Errno::EAGAIN, Errno::EWOULDBLOCK, IO::WaitReadable
|
122
|
+
IO.select([@socket])
|
123
|
+
retry
|
124
|
+
rescue IO::WaitWritable
|
125
|
+
IO.select(nil, [@socket])
|
126
|
+
retry
|
127
|
+
end
|
128
|
+
else
|
129
|
+
@socket.connect
|
130
|
+
end
|
131
|
+
rescue Errno::ETIMEDOUT, Timeout::Error
|
132
|
+
raise Excon::Errors::Timeout.new('connect timeout reached')
|
133
|
+
end
|
134
|
+
|
135
|
+
# verify connection
|
136
|
+
if @data[:ssl_verify_peer]
|
137
|
+
@socket.post_connection_check(@data[:ssl_verify_peer_host] || @data[:host])
|
138
|
+
end
|
139
|
+
|
140
|
+
@socket
|
141
|
+
end
|
142
|
+
|
143
|
+
private
|
144
|
+
|
145
|
+
def connect
|
146
|
+
# backwards compatability for things lacking nonblock
|
147
|
+
@nonblock = HAVE_NONBLOCK && @nonblock
|
148
|
+
super
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Excon
|
2
|
+
class StandardInstrumentor
|
3
|
+
def self.instrument(name, params = {}, &block)
|
4
|
+
params = params.dup
|
5
|
+
|
6
|
+
# reduce duplication/noise of output
|
7
|
+
params.delete(:connection)
|
8
|
+
params.delete(:stack)
|
9
|
+
|
10
|
+
if params.has_key?(:headers) && params[:headers].has_key?('Authorization')
|
11
|
+
params[:headers] = params[:headers].dup
|
12
|
+
params[:headers]['Authorization'] = REDACTED
|
13
|
+
end
|
14
|
+
|
15
|
+
if params.has_key?(:password)
|
16
|
+
params[:password] = REDACTED
|
17
|
+
end
|
18
|
+
|
19
|
+
$stderr.puts(name)
|
20
|
+
Excon::PrettyPrinter.pp($stderr, params)
|
21
|
+
|
22
|
+
if block_given?
|
23
|
+
yield
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|