httpclient-fixcerts 2.8.5

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.
Files changed (69) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +98 -0
  3. data/bin/httpclient +77 -0
  4. data/bin/jsonclient +85 -0
  5. data/lib/hexdump.rb +50 -0
  6. data/lib/http-access2/cookie.rb +1 -0
  7. data/lib/http-access2/http.rb +1 -0
  8. data/lib/http-access2.rb +55 -0
  9. data/lib/httpclient/auth.rb +924 -0
  10. data/lib/httpclient/cacert.pem +3952 -0
  11. data/lib/httpclient/cacert1024.pem +3866 -0
  12. data/lib/httpclient/connection.rb +88 -0
  13. data/lib/httpclient/cookie.rb +220 -0
  14. data/lib/httpclient/http.rb +1082 -0
  15. data/lib/httpclient/include_client.rb +85 -0
  16. data/lib/httpclient/jruby_ssl_socket.rb +594 -0
  17. data/lib/httpclient/session.rb +960 -0
  18. data/lib/httpclient/ssl_config.rb +433 -0
  19. data/lib/httpclient/ssl_socket.rb +150 -0
  20. data/lib/httpclient/timeout.rb +140 -0
  21. data/lib/httpclient/util.rb +222 -0
  22. data/lib/httpclient/version.rb +3 -0
  23. data/lib/httpclient/webagent-cookie.rb +459 -0
  24. data/lib/httpclient.rb +1332 -0
  25. data/lib/jsonclient.rb +66 -0
  26. data/lib/oauthclient.rb +111 -0
  27. data/sample/async.rb +8 -0
  28. data/sample/auth.rb +11 -0
  29. data/sample/cookie.rb +18 -0
  30. data/sample/dav.rb +103 -0
  31. data/sample/howto.rb +49 -0
  32. data/sample/jsonclient.rb +67 -0
  33. data/sample/oauth_buzz.rb +57 -0
  34. data/sample/oauth_friendfeed.rb +59 -0
  35. data/sample/oauth_twitter.rb +61 -0
  36. data/sample/ssl/0cert.pem +22 -0
  37. data/sample/ssl/0key.pem +30 -0
  38. data/sample/ssl/1000cert.pem +19 -0
  39. data/sample/ssl/1000key.pem +18 -0
  40. data/sample/ssl/htdocs/index.html +10 -0
  41. data/sample/ssl/ssl_client.rb +22 -0
  42. data/sample/ssl/webrick_httpsd.rb +29 -0
  43. data/sample/stream.rb +21 -0
  44. data/sample/thread.rb +27 -0
  45. data/sample/wcat.rb +21 -0
  46. data/test/ca-chain.pem +44 -0
  47. data/test/ca.cert +23 -0
  48. data/test/client-pass.key +18 -0
  49. data/test/client.cert +19 -0
  50. data/test/client.key +15 -0
  51. data/test/helper.rb +131 -0
  52. data/test/htdigest +1 -0
  53. data/test/htpasswd +2 -0
  54. data/test/jruby_ssl_socket/test_pemutils.rb +32 -0
  55. data/test/runner.rb +2 -0
  56. data/test/server.cert +19 -0
  57. data/test/server.key +15 -0
  58. data/test/sslsvr.rb +65 -0
  59. data/test/subca.cert +21 -0
  60. data/test/test_auth.rb +492 -0
  61. data/test/test_cookie.rb +309 -0
  62. data/test/test_hexdump.rb +14 -0
  63. data/test/test_http-access2.rb +508 -0
  64. data/test/test_httpclient.rb +2145 -0
  65. data/test/test_include_client.rb +52 -0
  66. data/test/test_jsonclient.rb +98 -0
  67. data/test/test_ssl.rb +562 -0
  68. data/test/test_webagent-cookie.rb +465 -0
  69. metadata +124 -0
data/lib/httpclient.rb ADDED
@@ -0,0 +1,1332 @@
1
+ # HTTPClient - HTTP client library.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
+ #
4
+ # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
+ # redistribute it and/or modify it under the same terms of Ruby's license;
6
+ # either the dual license version in 2003, or any later version.
7
+
8
+
9
+ require 'stringio'
10
+ require 'digest/sha1'
11
+
12
+ # Extra library
13
+ require 'httpclient/version'
14
+ require 'httpclient/util'
15
+ require 'httpclient/ssl_config'
16
+ require 'httpclient/connection'
17
+ require 'httpclient/session'
18
+ require 'httpclient/http'
19
+ require 'httpclient/auth'
20
+ require 'httpclient/cookie'
21
+
22
+ # :main:HTTPClient
23
+ # The HTTPClient class provides several methods for accessing Web resources
24
+ # via HTTP.
25
+ #
26
+ # HTTPClient instance is designed to be MT-safe. You can call a HTTPClient
27
+ # instance from several threads without synchronization after setting up an
28
+ # instance.
29
+ #
30
+ # clnt = HTTPClient.new
31
+ # clnt.set_cookie_store('/home/nahi/cookie.dat')
32
+ # urls.each do |url|
33
+ # Thread.new(url) do |u|
34
+ # p clnt.head(u).status
35
+ # end
36
+ # end
37
+ #
38
+ # == How to use
39
+ #
40
+ # At first, how to create your client. See initialize for more detail.
41
+ #
42
+ # 1. Create simple client.
43
+ #
44
+ # clnt = HTTPClient.new
45
+ #
46
+ # 2. Accessing resources through HTTP proxy. You can use environment
47
+ # variable 'http_proxy' or 'HTTP_PROXY' instead.
48
+ #
49
+ # clnt = HTTPClient.new('http://myproxy:8080')
50
+ #
51
+ # === How to retrieve web resources
52
+ #
53
+ # See get and get_content.
54
+ #
55
+ # 1. Get content of specified URL. It returns HTTP::Message object and
56
+ # calling 'body' method of it returns a content String.
57
+ #
58
+ # puts clnt.get('http://dev.ctor.org/').body
59
+ #
60
+ # 2. For getting content directly, use get_content. It follows redirect
61
+ # response and returns a String of whole result.
62
+ #
63
+ # puts clnt.get_content('http://dev.ctor.org/')
64
+ #
65
+ # 3. You can pass :follow_redirect option to follow redirect response in get.
66
+ #
67
+ # puts clnt.get('http://dev.ctor.org/', :follow_redirect => true)
68
+ #
69
+ # 4. Get content as chunks of String. It yields chunks of String.
70
+ #
71
+ # clnt.get_content('http://dev.ctor.org/') do |chunk|
72
+ # puts chunk
73
+ # end
74
+ #
75
+ # === Invoking other HTTP methods
76
+ #
77
+ # See head, get, post, put, delete, options, propfind, proppatch and trace.
78
+ # It returns a HTTP::Message instance as a response.
79
+ #
80
+ # 1. Do HEAD request.
81
+ #
82
+ # res = clnt.head(uri)
83
+ # p res.header['Last-Modified'][0]
84
+ #
85
+ # 2. Do GET request with query.
86
+ #
87
+ # query = { 'keyword' => 'ruby', 'lang' => 'en' }
88
+ # res = clnt.get(uri, query)
89
+ # p res.status
90
+ # p res.contenttype
91
+ # p res.header['X-Custom']
92
+ # puts res.body
93
+ #
94
+ # You can also use keyword argument style.
95
+ #
96
+ # res = clnt.get(uri, :query => { :keyword => 'ruby', :lang => 'en' })
97
+ #
98
+ # === How to POST
99
+ #
100
+ # See post.
101
+ #
102
+ # 1. Do POST a form data.
103
+ #
104
+ # body = { 'keyword' => 'ruby', 'lang' => 'en' }
105
+ # res = clnt.post(uri, body)
106
+ #
107
+ # Keyword argument style.
108
+ #
109
+ # res = clnt.post(uri, :body => ...)
110
+ #
111
+ # 2. Do multipart file upload with POST. No need to set extra header by
112
+ # yourself from httpclient/2.1.4.
113
+ #
114
+ # File.open('/tmp/post_data') do |file|
115
+ # body = { 'upload' => file, 'user' => 'nahi' }
116
+ # res = clnt.post(uri, body)
117
+ # end
118
+ #
119
+ # 3. Do multipart with custom body.
120
+ #
121
+ # File.open('/tmp/post_data') do |file|
122
+ # body = [{ 'Content-Type' => 'application/atom+xml; charset=UTF-8',
123
+ # :content => '<entry>...</entry>' },
124
+ # { 'Content-Type' => 'video/mp4',
125
+ # 'Content-Transfer-Encoding' => 'binary',
126
+ # :content => file }]
127
+ # res = clnt.post(uri, body)
128
+ # end
129
+ #
130
+ # === Accessing via SSL
131
+ #
132
+ # Ruby needs to be compiled with OpenSSL.
133
+ #
134
+ # 1. Get content of specified URL via SSL.
135
+ # Just pass an URL which starts with 'https://'.
136
+ #
137
+ # https_url = 'https://www.rsa.com'
138
+ # clnt.get(https_url)
139
+ #
140
+ # 2. Getting peer certificate from response.
141
+ #
142
+ # res = clnt.get(https_url)
143
+ # p res.peer_cert #=> returns OpenSSL::X509::Certificate
144
+ #
145
+ # 3. Configuring OpenSSL options. See HTTPClient::SSLConfig for more details.
146
+ #
147
+ # user_cert_file = 'cert.pem'
148
+ # user_key_file = 'privkey.pem'
149
+ # clnt.ssl_config.set_client_cert_file(user_cert_file, user_key_file)
150
+ # clnt.get(https_url)
151
+ #
152
+ # 4. Revocation check. On JRuby you can set following options to let
153
+ # HTTPClient to perform revocation check with CRL and OCSP:
154
+ #
155
+ # -J-Dcom.sun.security.enableCRLDP=true -J-Dcom.sun.net.ssl.checkRevocation=true
156
+ # ex. jruby -J-Dcom.sun.security.enableCRLDP=true -J-Dcom.sun.net.ssl.checkRevocation=true app.rb
157
+ # Revoked cert example: https://test-sspev.verisign.com:2443/test-SSPEV-revoked-verisign.html
158
+ #
159
+ # On other platform you can download CRL by yourself and set it via
160
+ # SSLConfig#add_crl.
161
+ #
162
+ # === Handling Cookies
163
+ #
164
+ # 1. Using volatile Cookies. Nothing to do. HTTPClient handles Cookies.
165
+ #
166
+ # clnt = HTTPClient.new
167
+ # res = clnt.get(url1) # receives Cookies.
168
+ # res = clnt.get(url2) # sends Cookies if needed.
169
+ # p res.cookies
170
+ #
171
+ # 2. Saving non volatile Cookies to a specified file. Need to set a file at
172
+ # first and invoke save method at last.
173
+ #
174
+ # clnt = HTTPClient.new
175
+ # clnt.set_cookie_store('/home/nahi/cookie.dat')
176
+ # clnt.get(url)
177
+ # ...
178
+ # clnt.save_cookie_store
179
+ #
180
+ # 3. Disabling Cookies.
181
+ #
182
+ # clnt = HTTPClient.new
183
+ # clnt.cookie_manager = nil
184
+ #
185
+ # === Configuring authentication credentials
186
+ #
187
+ # 1. Authentication with Web server. Supports BasicAuth, DigestAuth, and
188
+ # Negotiate/NTLM (requires ruby/ntlm module).
189
+ #
190
+ # clnt = HTTPClient.new
191
+ # domain = 'http://dev.ctor.org/http-access2/'
192
+ # user = 'user'
193
+ # password = 'user'
194
+ # clnt.set_auth(domain, user, password)
195
+ # p clnt.get('http://dev.ctor.org/http-access2/login').status
196
+ #
197
+ # 2. Authentication with Proxy server. Supports BasicAuth and NTLM
198
+ # (requires win32/sspi)
199
+ #
200
+ # clnt = HTTPClient.new(proxy)
201
+ # user = 'proxy'
202
+ # password = 'proxy'
203
+ # clnt.set_proxy_auth(user, password)
204
+ # p clnt.get(url)
205
+ #
206
+ # === Invoking HTTP methods with custom header
207
+ #
208
+ # Pass a Hash or an Array for header argument.
209
+ #
210
+ # header = { 'Accept' => 'text/html' }
211
+ # clnt.get(uri, query, header)
212
+ #
213
+ # header = [['Accept', 'image/jpeg'], ['Accept', 'image/png']]
214
+ # clnt.get_content(uri, query, header)
215
+ #
216
+ # === Invoking HTTP methods asynchronously
217
+ #
218
+ # See head_async, get_async, post_async, put_async, delete_async,
219
+ # options_async, propfind_async, proppatch_async, and trace_async.
220
+ # It immediately returns a HTTPClient::Connection instance as a returning value.
221
+ #
222
+ # connection = clnt.post_async(url, body)
223
+ # print 'posting.'
224
+ # while true
225
+ # break if connection.finished?
226
+ # print '.'
227
+ # sleep 1
228
+ # end
229
+ # puts '.'
230
+ # res = connection.pop
231
+ # p res.status
232
+ # p res.body.read # res.body is an IO for the res of async method.
233
+ #
234
+ # === Shortcut methods
235
+ #
236
+ # You can invoke get_content, get, etc. without creating HTTPClient instance.
237
+ #
238
+ # ruby -rhttpclient -e 'puts HTTPClient.get_content(ARGV.shift)' http://dev.ctor.org/
239
+ # ruby -rhttpclient -e 'p HTTPClient.head(ARGV.shift).header["last-modified"]' http://dev.ctor.org/
240
+ #
241
+ class HTTPClient
242
+ RUBY_VERSION_STRING = "ruby #{RUBY_VERSION} (#{RUBY_RELEASE_DATE})"
243
+ LIB_NAME = "(#{VERSION}, #{RUBY_VERSION_STRING})"
244
+
245
+ include Util
246
+
247
+ # Raised for indicating running environment configuration error for example
248
+ # accessing via SSL under the ruby which is not compiled with OpenSSL.
249
+ class ConfigurationError < StandardError
250
+ end
251
+
252
+ # Raised for indicating HTTP response error.
253
+ class BadResponseError < RuntimeError
254
+ # HTTP::Message:: a response
255
+ attr_reader :res
256
+
257
+ def initialize(msg, res = nil) # :nodoc:
258
+ super(msg)
259
+ @res = res
260
+ end
261
+ end
262
+
263
+ # Raised for indicating a timeout error.
264
+ class TimeoutError < RuntimeError
265
+ end
266
+
267
+ # Raised for indicating a connection timeout error.
268
+ # You can configure connection timeout via HTTPClient#connect_timeout=.
269
+ class ConnectTimeoutError < TimeoutError
270
+ end
271
+
272
+ # Raised for indicating a request sending timeout error.
273
+ # You can configure request sending timeout via HTTPClient#send_timeout=.
274
+ class SendTimeoutError < TimeoutError
275
+ end
276
+
277
+ # Raised for indicating a response receiving timeout error.
278
+ # You can configure response receiving timeout via
279
+ # HTTPClient#receive_timeout=.
280
+ class ReceiveTimeoutError < TimeoutError
281
+ end
282
+
283
+ # Deprecated. just for backward compatibility
284
+ class Session
285
+ BadResponse = ::HTTPClient::BadResponseError
286
+ end
287
+
288
+ class << self
289
+ %w(get_content post_content head get post put delete options propfind proppatch trace).each do |name|
290
+ eval <<-EOD
291
+ def #{name}(*arg, &block)
292
+ clnt = new
293
+ begin
294
+ clnt.#{name}(*arg, &block)
295
+ ensure
296
+ clnt.reset_all
297
+ end
298
+ end
299
+ EOD
300
+ end
301
+
302
+ private
303
+
304
+ def attr_proxy(symbol, assignable = false)
305
+ name = symbol.to_s
306
+ define_method(name) {
307
+ @session_manager.__send__(name)
308
+ }
309
+ if assignable
310
+ aname = name + '='
311
+ define_method(aname) { |rhs|
312
+ @session_manager.__send__(aname, rhs)
313
+ }
314
+ end
315
+ end
316
+ end
317
+
318
+ # HTTPClient::SSLConfig:: SSL configurator.
319
+ attr_reader :ssl_config
320
+ # HTTPClient::CookieManager:: Cookies configurator.
321
+ attr_accessor :cookie_manager
322
+ # An array of response HTTP message body String which is used for loop-back
323
+ # test. See test/* to see how to use it. If you want to do loop-back test
324
+ # of HTTP header, use test_loopback_http_response instead.
325
+ attr_reader :test_loopback_response
326
+ # An array of request filter which can trap HTTP request/response.
327
+ # See HTTPClient::WWWAuth to see how to use it.
328
+ attr_reader :request_filter
329
+ # HTTPClient::ProxyAuth:: Proxy authentication handler.
330
+ attr_reader :proxy_auth
331
+ # HTTPClient::WWWAuth:: WWW authentication handler.
332
+ attr_reader :www_auth
333
+ # How many times get_content and post_content follows HTTP redirect.
334
+ # 10 by default.
335
+ attr_accessor :follow_redirect_count
336
+ # Base url of resources.
337
+ attr_accessor :base_url
338
+ # Default request header.
339
+ attr_accessor :default_header
340
+
341
+ # Set HTTP version as a String:: 'HTTP/1.0' or 'HTTP/1.1'
342
+ attr_proxy(:protocol_version, true)
343
+ # Connect timeout in sec.
344
+ attr_proxy(:connect_timeout, true)
345
+ # Request sending timeout in sec.
346
+ attr_proxy(:send_timeout, true)
347
+ # Response receiving timeout in sec.
348
+ attr_proxy(:receive_timeout, true)
349
+ # Reuse the same connection within this timeout in sec. from last used.
350
+ attr_proxy(:keep_alive_timeout, true)
351
+ # Size of reading block for non-chunked response.
352
+ attr_proxy(:read_block_size, true)
353
+ # Negotiation retry count for authentication. 5 by default.
354
+ attr_proxy(:protocol_retry_count, true)
355
+ # if your ruby is older than 2005-09-06, do not set socket_sync = false to
356
+ # avoid an SSL socket blocking bug in openssl/buffering.rb.
357
+ attr_proxy(:socket_sync, true)
358
+ # Enables TCP keepalive; no timing settings exist at present
359
+ attr_proxy(:tcp_keepalive, true)
360
+ # User-Agent header in HTTP request.
361
+ attr_proxy(:agent_name, true)
362
+ # From header in HTTP request.
363
+ attr_proxy(:from, true)
364
+ # An array of response HTTP String (not a HTTP message body) which is used
365
+ # for loopback test. See test/* to see how to use it.
366
+ attr_proxy(:test_loopback_http_response)
367
+ # Decompress a compressed (with gzip or deflate) content body transparently. false by default.
368
+ attr_proxy(:transparent_gzip_decompression, true)
369
+ # Raise BadResponseError if response size does not match with Content-Length header in response. false by default.
370
+ # TODO: enable by default
371
+ attr_proxy(:strict_response_size_check, true)
372
+ # Local socket address. Set HTTPClient#socket_local.host and HTTPClient#socket_local.port to specify local binding hostname and port of TCP socket.
373
+ attr_proxy(:socket_local, true)
374
+
375
+ # Default header for PROPFIND request.
376
+ PROPFIND_DEFAULT_EXTHEADER = { 'Depth' => '0' }
377
+
378
+ # Default User-Agent header
379
+ DEFAULT_AGENT_NAME = 'HTTPClient/1.0'
380
+
381
+ # Creates a HTTPClient instance which manages sessions, cookies, etc.
382
+ #
383
+ # HTTPClient.new takes optional arguments as a Hash.
384
+ # * :proxy - proxy url string
385
+ # * :agent_name - User-Agent String
386
+ # * :from - from header String
387
+ # * :base_url - base URL of resources
388
+ # * :default_header - header Hash all HTTP requests should have
389
+ # * :force_basic_auth - flag for sending Authorization header w/o gettin 401 first
390
+ # User-Agent and From are embedded in HTTP request Header if given.
391
+ # From header is not set without setting it explicitly.
392
+ #
393
+ # proxy = 'http://myproxy:8080'
394
+ # agent_name = 'MyAgent/0.1'
395
+ # from = 'from@example.com'
396
+ # HTTPClient.new(proxy, agent_name, from)
397
+ #
398
+ # After you set :base_url, all resources you pass to get, post and other
399
+ # methods are recognized to be prefixed with base_url. Say base_url is
400
+ # 'https://api.example.com/v1/, get('users') is the same as
401
+ # get('https://api.example.com/v1/users') internally. You can also pass
402
+ # full URL from 'http://' even after setting base_url.
403
+ #
404
+ # The expected base_url and path behavior is the following. Please take
405
+ # care of '/' in base_url and path.
406
+ #
407
+ # The last '/' is important for base_url:
408
+ # 1. http://foo/bar/baz/ + path -> http://foo/bar/baz/path
409
+ # 2. http://foo/bar/baz + path -> http://foo/bar/path
410
+ # Relative path handling:
411
+ # 3. http://foo/bar/baz/ + ../path -> http://foo/bar/path
412
+ # 4. http://foo/bar/baz + ../path -> http://foo/path
413
+ # 5. http://foo/bar/baz/ + ./path -> http://foo/bar/baz/path
414
+ # 6. http://foo/bar/baz + ./path -> http://foo/bar/path
415
+ # The leading '/' of path means absolute path:
416
+ # 7. http://foo/bar/baz/ + /path -> http://foo/path
417
+ # 8. http://foo/bar/baz + /path -> http://foo/path
418
+ #
419
+ # :default_header is for providing default headers Hash that all HTTP
420
+ # requests should have, such as custom 'Authorization' header in API.
421
+ # You can override :default_header with :header Hash parameter in HTTP
422
+ # request methods.
423
+ #
424
+ # :force_basic_auth turns on/off the BasicAuth force flag. Generally
425
+ # HTTP client must send Authorization header after it gets 401 error
426
+ # from server from security reason. But in some situation (e.g. API
427
+ # client) you might want to send Authorization from the beginning.
428
+ def initialize(*args, &block)
429
+ proxy, agent_name, from, base_url, default_header, force_basic_auth =
430
+ keyword_argument(args, :proxy, :agent_name, :from, :base_url, :default_header, :force_basic_auth)
431
+ @proxy = nil # assigned later.
432
+ @no_proxy = nil
433
+ @no_proxy_regexps = []
434
+ @base_url = base_url
435
+ @default_header = default_header || {}
436
+ @www_auth = WWWAuth.new
437
+ @proxy_auth = ProxyAuth.new
438
+ @www_auth.basic_auth.force_auth = @proxy_auth.basic_auth.force_auth = force_basic_auth
439
+ @request_filter = [@proxy_auth, @www_auth]
440
+ @debug_dev = nil
441
+ @redirect_uri_callback = method(:default_redirect_uri_callback)
442
+ @test_loopback_response = []
443
+ @session_manager = SessionManager.new(self)
444
+ @session_manager.agent_name = agent_name || DEFAULT_AGENT_NAME
445
+ @session_manager.from = from
446
+ @session_manager.ssl_config = @ssl_config = SSLConfig.new(self)
447
+ @cookie_manager = CookieManager.new
448
+ @follow_redirect_count = 10
449
+ load_environment
450
+ self.proxy = proxy if proxy
451
+ keep_webmock_compat
452
+ instance_eval(&block) if block
453
+ @session_manager.ssl_config.set_default_paths
454
+ end
455
+
456
+ # webmock 1.6.2 depends on HTTP::Message#body.content to work.
457
+ # let's keep it work iif webmock is loaded for a while.
458
+ def keep_webmock_compat
459
+ if respond_to?(:do_get_block_with_webmock)
460
+ ::HTTP::Message.module_eval do
461
+ def body
462
+ def (o = self.content).content
463
+ self
464
+ end
465
+ o
466
+ end
467
+ end
468
+ end
469
+ end
470
+
471
+ # Returns debug device if exists. See debug_dev=.
472
+ def debug_dev
473
+ @debug_dev
474
+ end
475
+
476
+ # Sets debug device. Once debug device is set, all HTTP requests and
477
+ # responses are dumped to given device. dev must respond to << for dump.
478
+ #
479
+ # Calling this method resets all existing sessions.
480
+ def debug_dev=(dev)
481
+ @debug_dev = dev
482
+ reset_all
483
+ @session_manager.debug_dev = dev
484
+ end
485
+
486
+ # Returns URI object of HTTP proxy if exists.
487
+ def proxy
488
+ @proxy
489
+ end
490
+
491
+ # Sets HTTP proxy used for HTTP connection. Given proxy can be an URI,
492
+ # a String or nil. You can set user/password for proxy authentication like
493
+ # HTTPClient#proxy = 'http://user:passwd@myproxy:8080'
494
+ #
495
+ # You can use environment variable 'http_proxy' or 'HTTP_PROXY' for it.
496
+ # You need to use 'cgi_http_proxy' or 'CGI_HTTP_PROXY' instead if you run
497
+ # HTTPClient from CGI environment from security reason. (HTTPClient checks
498
+ # 'REQUEST_METHOD' environment variable whether it's CGI or not)
499
+ #
500
+ # Calling this method resets all existing sessions.
501
+ def proxy=(proxy)
502
+ if proxy.nil? || proxy.to_s.empty?
503
+ @proxy = nil
504
+ @proxy_auth.reset_challenge
505
+ else
506
+ @proxy = urify(proxy)
507
+ if @proxy.scheme == nil or @proxy.scheme.downcase != 'http' or
508
+ @proxy.host == nil or @proxy.port == nil
509
+ raise ArgumentError.new("unsupported proxy #{proxy}")
510
+ end
511
+ @proxy_auth.reset_challenge
512
+ if @proxy.user || @proxy.password
513
+ @proxy_auth.set_auth(@proxy.user, @proxy.password)
514
+ end
515
+ end
516
+ reset_all
517
+ @session_manager.proxy = @proxy
518
+ @proxy
519
+ end
520
+
521
+ # Returns NO_PROXY setting String if given.
522
+ def no_proxy
523
+ @no_proxy
524
+ end
525
+
526
+ # Sets NO_PROXY setting String. no_proxy must be a comma separated String.
527
+ # Each entry must be 'host' or 'host:port' such as;
528
+ # HTTPClient#no_proxy = 'example.com,example.co.jp:443'
529
+ #
530
+ # 'localhost' is treated as a no_proxy site regardless of explicitly listed.
531
+ # HTTPClient checks given URI objects before accessing it.
532
+ # 'host' is tail string match. No IP-addr conversion.
533
+ #
534
+ # You can use environment variable 'no_proxy' or 'NO_PROXY' for it.
535
+ #
536
+ # Calling this method resets all existing sessions.
537
+ def no_proxy=(no_proxy)
538
+ @no_proxy = no_proxy
539
+ @no_proxy_regexps.clear
540
+ if @no_proxy
541
+ @no_proxy.scan(/([^\s:,]+)(?::(\d+))?/) do |host, port|
542
+ if host[0] == ?.
543
+ regexp = /#{Regexp.quote(host)}\z/i
544
+ else
545
+ regexp = /(\A|\.)#{Regexp.quote(host)}\z/i
546
+ end
547
+ @no_proxy_regexps << [regexp, port]
548
+ end
549
+ end
550
+ reset_all
551
+ end
552
+
553
+ # Sets credential for Web server authentication.
554
+ # domain:: a String or an URI to specify where HTTPClient should use this
555
+ # credential. If you set uri to nil, HTTPClient uses this credential
556
+ # wherever a server requires it.
557
+ # user:: username String.
558
+ # passwd:: password String.
559
+ #
560
+ # You can set multiple credentials for each uri.
561
+ #
562
+ # clnt.set_auth('http://www.example.com/foo/', 'foo_user', 'passwd')
563
+ # clnt.set_auth('http://www.example.com/bar/', 'bar_user', 'passwd')
564
+ #
565
+ # Calling this method resets all existing sessions.
566
+ def set_auth(domain, user, passwd)
567
+ uri = to_resource_url(domain)
568
+ @www_auth.set_auth(uri, user, passwd)
569
+ reset_all
570
+ end
571
+
572
+ # Deprecated. Use set_auth instead.
573
+ def set_basic_auth(domain, user, passwd)
574
+ uri = to_resource_url(domain)
575
+ @www_auth.basic_auth.set(uri, user, passwd)
576
+ reset_all
577
+ end
578
+
579
+ # Sets credential for Proxy authentication.
580
+ # user:: username String.
581
+ # passwd:: password String.
582
+ #
583
+ # Calling this method resets all existing sessions.
584
+ def set_proxy_auth(user, passwd)
585
+ @proxy_auth.set_auth(user, passwd)
586
+ reset_all
587
+ end
588
+
589
+ # Turn on/off the BasicAuth force flag. Generally HTTP client must
590
+ # send Authorization header after it gets 401 error from server from
591
+ # security reason. But in some situation (e.g. API client) you might
592
+ # want to send Authorization from the beginning.
593
+ def force_basic_auth=(force_basic_auth)
594
+ @www_auth.basic_auth.force_auth = @proxy_auth.basic_auth.force_auth = force_basic_auth
595
+ end
596
+
597
+ # Sets the filename where non-volatile Cookies be saved by calling
598
+ # save_cookie_store.
599
+ # This method tries to load and managing Cookies from the specified file.
600
+ #
601
+ # Calling this method resets all existing sessions.
602
+ def set_cookie_store(filename)
603
+ @cookie_manager.cookies_file = filename
604
+ @cookie_manager.load_cookies if filename
605
+ reset_all
606
+ end
607
+
608
+ # Try to save Cookies to the file specified in set_cookie_store. Unexpected
609
+ # error will be raised if you don't call set_cookie_store first.
610
+ def save_cookie_store
611
+ @cookie_manager.save_cookies
612
+ end
613
+
614
+ # Returns stored cookies.
615
+ def cookies
616
+ if @cookie_manager
617
+ @cookie_manager.cookies
618
+ end
619
+ end
620
+
621
+ # Sets callback proc when HTTP redirect status is returned for get_content
622
+ # and post_content. default_redirect_uri_callback is used by default.
623
+ #
624
+ # If you need strict implementation which does not allow relative URI
625
+ # redirection, set strict_redirect_uri_callback instead.
626
+ #
627
+ # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
628
+ #
629
+ def redirect_uri_callback=(redirect_uri_callback)
630
+ @redirect_uri_callback = redirect_uri_callback
631
+ end
632
+
633
+ # Retrieves a web resource.
634
+ #
635
+ # uri:: a String or an URI object which represents an URL of web resource.
636
+ # query:: a Hash or an Array of query part of URL.
637
+ # e.g. { "a" => "b" } => 'http://host/part?a=b'.
638
+ # Give an array to pass multiple value like
639
+ # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'.
640
+ # header:: a Hash or an Array of extra headers. e.g.
641
+ # { 'Accept' => 'text/html' } or
642
+ # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
643
+ # &block:: Give a block to get chunked message-body of response like
644
+ # get_content(uri) { |chunked_body| ... }.
645
+ # Size of each chunk may not be the same.
646
+ #
647
+ # get_content follows HTTP redirect status (see HTTP::Status.redirect?)
648
+ # internally and try to retrieve content from redirected URL. See
649
+ # redirect_uri_callback= how HTTP redirection is handled.
650
+ #
651
+ # If you need to get full HTTP response including HTTP status and headers,
652
+ # use get method. get returns HTTP::Message as a response and you need to
653
+ # follow HTTP redirect by yourself if you need.
654
+ def get_content(uri, *args, &block)
655
+ query, header = keyword_argument(args, :query, :header)
656
+ success_content(follow_redirect(:get, uri, query, nil, header || {}, &block))
657
+ end
658
+
659
+ # Posts a content.
660
+ #
661
+ # uri:: a String or an URI object which represents an URL of web resource.
662
+ # body:: a Hash or an Array of body part. e.g.
663
+ # { "a" => "b" } => 'a=b'
664
+ # Give an array to pass multiple value like
665
+ # [["a", "b"], ["a", "c"]] => 'a=b&a=c'
666
+ # When you pass a File as a value, it will be posted as a
667
+ # multipart/form-data. e.g.
668
+ # { 'upload' => file }
669
+ # You can also send custom multipart by passing an array of hashes.
670
+ # Each part must have a :content attribute which can be a file, all
671
+ # other keys will become headers.
672
+ # [{ 'Content-Type' => 'text/plain', :content => "some text" },
673
+ # { 'Content-Type' => 'video/mp4', :content => File.new('video.mp4') }]
674
+ # => <Two parts with custom Content-Type header>
675
+ # header:: a Hash or an Array of extra headers. e.g.
676
+ # { 'Accept' => 'text/html' }
677
+ # or
678
+ # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
679
+ # &block:: Give a block to get chunked message-body of response like
680
+ # post_content(uri) { |chunked_body| ... }.
681
+ # Size of each chunk may not be the same.
682
+ #
683
+ # post_content follows HTTP redirect status (see HTTP::Status.redirect?)
684
+ # internally and try to post the content to redirected URL. See
685
+ # redirect_uri_callback= how HTTP redirection is handled.
686
+ # Bear in mind that you should not depend on post_content because it sends
687
+ # the same POST method to the new location which is prohibited in HTTP spec.
688
+ #
689
+ # If you need to get full HTTP response including HTTP status and headers,
690
+ # use post method.
691
+ def post_content(uri, *args, &block)
692
+ if hashy_argument_has_keys(args, :query, :body)
693
+ query, body, header = keyword_argument(args, :query, :body, :header)
694
+ else
695
+ query = nil
696
+ body, header = keyword_argument(args, :body, :header)
697
+ end
698
+ success_content(follow_redirect(:post, uri, query, body, header || {}, &block))
699
+ end
700
+
701
+ # A method for redirect uri callback. How to use:
702
+ # clnt.redirect_uri_callback = clnt.method(:strict_redirect_uri_callback)
703
+ # This callback does not allow relative redirect such as
704
+ # Location: ../foo/
705
+ # in HTTP header. (raises BadResponseError instead)
706
+ def strict_redirect_uri_callback(uri, res)
707
+ newuri = urify(res.header['location'][0])
708
+ if https?(uri) && !https?(newuri)
709
+ raise BadResponseError.new("redirecting to non-https resource")
710
+ end
711
+ if !http?(newuri) && !https?(newuri)
712
+ raise BadResponseError.new("unexpected location: #{newuri}", res)
713
+ end
714
+ puts "redirect to: #{newuri}" if $DEBUG
715
+ newuri
716
+ end
717
+
718
+ # A default method for redirect uri callback. This method is used by
719
+ # HTTPClient instance by default.
720
+ # This callback allows relative redirect such as
721
+ # Location: ../foo/
722
+ # in HTTP header.
723
+ def default_redirect_uri_callback(uri, res)
724
+ newuri = urify(res.header['location'][0])
725
+ if !http?(newuri) && !https?(newuri)
726
+ warn("#{newuri}: a relative URI in location header which is not recommended")
727
+ warn("'The field value consists of a single absolute URI' in HTTP spec")
728
+ newuri = uri + newuri
729
+ end
730
+ if https?(uri) && !https?(newuri)
731
+ raise BadResponseError.new("redirecting to non-https resource")
732
+ end
733
+ puts "redirect to: #{newuri}" if $DEBUG
734
+ newuri
735
+ end
736
+
737
+ # Sends HEAD request to the specified URL. See request for arguments.
738
+ def head(uri, *args)
739
+ request(:head, uri, argument_to_hash(args, :query, :header, :follow_redirect))
740
+ end
741
+
742
+ # Sends GET request to the specified URL. See request for arguments.
743
+ def get(uri, *args, &block)
744
+ request(:get, uri, argument_to_hash(args, :query, :header, :follow_redirect), &block)
745
+ end
746
+
747
+ # Sends PATCH request to the specified URL. See request for arguments.
748
+ def patch(uri, *args, &block)
749
+ if hashy_argument_has_keys(args, :query, :body)
750
+ new_args = args[0]
751
+ else
752
+ new_args = argument_to_hash(args, :body, :header)
753
+ end
754
+ request(:patch, uri, new_args, &block)
755
+ end
756
+
757
+ # :call-seq:
758
+ # post(uri, {query: query, body: body, header: header, follow_redirect: follow_redirect}) -> HTTP::Message
759
+ # post(uri, body, header, follow_redirect) -> HTTP::Message
760
+ #
761
+ # Sends POST request to the specified URL. See request for arguments.
762
+ # You should not depend on :follow_redirect => true for POST method. It
763
+ # sends the same POST method to the new location which is prohibited in HTTP spec.
764
+ def post(uri, *args, &block)
765
+ if hashy_argument_has_keys(args, :query, :body)
766
+ new_args = args[0]
767
+ else
768
+ new_args = argument_to_hash(args, :body, :header, :follow_redirect)
769
+ end
770
+ request(:post, uri, new_args, &block)
771
+ end
772
+
773
+ # Sends PUT request to the specified URL. See request for arguments.
774
+ def put(uri, *args, &block)
775
+ if hashy_argument_has_keys(args, :query, :body)
776
+ new_args = args[0]
777
+ else
778
+ new_args = argument_to_hash(args, :body, :header)
779
+ end
780
+ request(:put, uri, new_args, &block)
781
+ end
782
+
783
+ # Sends DELETE request to the specified URL. See request for arguments.
784
+ def delete(uri, *args, &block)
785
+ request(:delete, uri, argument_to_hash(args, :body, :header, :query), &block)
786
+ end
787
+
788
+ # Sends OPTIONS request to the specified URL. See request for arguments.
789
+ def options(uri, *args, &block)
790
+ new_args = argument_to_hash(args, :header, :query, :body)
791
+ request(:options, uri, new_args, &block)
792
+ end
793
+
794
+ # Sends PROPFIND request to the specified URL. See request for arguments.
795
+ def propfind(uri, *args, &block)
796
+ request(:propfind, uri, argument_to_hash(args, :header), &block)
797
+ end
798
+
799
+ # Sends PROPPATCH request to the specified URL. See request for arguments.
800
+ def proppatch(uri, *args, &block)
801
+ request(:proppatch, uri, argument_to_hash(args, :body, :header), &block)
802
+ end
803
+
804
+ # Sends TRACE request to the specified URL. See request for arguments.
805
+ def trace(uri, *args, &block)
806
+ request('TRACE', uri, argument_to_hash(args, :query, :header), &block)
807
+ end
808
+
809
+ # Sends a request to the specified URL.
810
+ #
811
+ # method:: HTTP method to be sent. method.to_s.upcase is used.
812
+ # uri:: a String or an URI object which represents an URL of web resource.
813
+ # query:: a Hash or an Array of query part of URL.
814
+ # e.g. { "a" => "b" } => 'http://host/part?a=b'
815
+ # Give an array to pass multiple value like
816
+ # [["a", "b"], ["a", "c"]] => 'http://host/part?a=b&a=c'
817
+ # body:: a Hash or an Array of body part. e.g.
818
+ # { "a" => "b" }
819
+ # => 'a=b'
820
+ # Give an array to pass multiple value like
821
+ # [["a", "b"], ["a", "c"]]
822
+ # => 'a=b&a=c'.
823
+ # When the given method is 'POST' and the given body contains a file
824
+ # as a value, it will be posted as a multipart/form-data. e.g.
825
+ # { 'upload' => file }
826
+ # You can also send custom multipart by passing an array of hashes.
827
+ # Each part must have a :content attribute which can be a file, all
828
+ # other keys will become headers.
829
+ # [{ 'Content-Type' => 'text/plain', :content => "some text" },
830
+ # { 'Content-Type' => 'video/mp4', :content => File.new('video.mp4') }]
831
+ # => <Two parts with custom Content-Type header>
832
+ # See HTTP::Message.file? for actual condition of 'a file'.
833
+ # header:: a Hash or an Array of extra headers. e.g.
834
+ # { 'Accept' => 'text/html' } or
835
+ # [['Accept', 'image/jpeg'], ['Accept', 'image/png']].
836
+ # &block:: Give a block to get chunked message-body of response like
837
+ # get(uri) { |chunked_body| ... }.
838
+ # Size of each chunk may not be the same.
839
+ #
840
+ # You can also pass a String as a body. HTTPClient just sends a String as
841
+ # a HTTP request message body.
842
+ #
843
+ # When you pass an IO as a body, HTTPClient sends it as a HTTP request with
844
+ # chunked encoding (Transfer-Encoding: chunked in HTTP header) if IO does not
845
+ # respond to :size. Bear in mind that some server application does not support
846
+ # chunked request. At least cgi.rb does not support it.
847
+ def request(method, uri, *args, &block)
848
+ query, body, header, follow_redirect = keyword_argument(args, :query, :body, :header, :follow_redirect)
849
+ if method == :propfind
850
+ header ||= PROPFIND_DEFAULT_EXTHEADER
851
+ else
852
+ header ||= {}
853
+ end
854
+ uri = to_resource_url(uri)
855
+ if block
856
+ filtered_block = adapt_block(&block)
857
+ end
858
+ if follow_redirect
859
+ follow_redirect(method, uri, query, body, header, &block)
860
+ else
861
+ do_request(method, uri, query, body, header, &filtered_block)
862
+ end
863
+ end
864
+
865
+ # Sends HEAD request in async style. See request_async for arguments.
866
+ # It immediately returns a HTTPClient::Connection instance as a result.
867
+ def head_async(uri, *args)
868
+ request_async2(:head, uri, argument_to_hash(args, :query, :header))
869
+ end
870
+
871
+ # Sends GET request in async style. See request_async for arguments.
872
+ # It immediately returns a HTTPClient::Connection instance as a result.
873
+ def get_async(uri, *args)
874
+ request_async2(:get, uri, argument_to_hash(args, :query, :header))
875
+ end
876
+
877
+ # Sends PATCH request in async style. See request_async2 for arguments.
878
+ # It immediately returns a HTTPClient::Connection instance as a result.
879
+ def patch_async(uri, *args)
880
+ if hashy_argument_has_keys(args, :query, :body)
881
+ new_args = args[0]
882
+ else
883
+ new_args = argument_to_hash(args, :body, :header)
884
+ end
885
+ request_async2(:patch, uri, new_args)
886
+ end
887
+
888
+ # Sends POST request in async style. See request_async for arguments.
889
+ # It immediately returns a HTTPClient::Connection instance as a result.
890
+ def post_async(uri, *args)
891
+ if hashy_argument_has_keys(args, :query, :body)
892
+ new_args = args[0]
893
+ else
894
+ new_args = argument_to_hash(args, :body, :header)
895
+ end
896
+ request_async2(:post, uri, new_args)
897
+ end
898
+
899
+ # Sends PUT request in async style. See request_async2 for arguments.
900
+ # It immediately returns a HTTPClient::Connection instance as a result.
901
+ def put_async(uri, *args)
902
+ if hashy_argument_has_keys(args, :query, :body)
903
+ new_args = args[0]
904
+ else
905
+ new_args = argument_to_hash(args, :body, :header)
906
+ end
907
+ request_async2(:put, uri, new_args)
908
+ end
909
+
910
+ # Sends DELETE request in async style. See request_async2 for arguments.
911
+ # It immediately returns a HTTPClient::Connection instance as a result.
912
+ def delete_async(uri, *args)
913
+ request_async2(:delete, uri, argument_to_hash(args, :body, :header, :query))
914
+ end
915
+
916
+ # Sends OPTIONS request in async style. See request_async2 for arguments.
917
+ # It immediately returns a HTTPClient::Connection instance as a result.
918
+ def options_async(uri, *args)
919
+ request_async2(:options, uri, argument_to_hash(args, :header, :query, :body))
920
+ end
921
+
922
+ # Sends PROPFIND request in async style. See request_async2 for arguments.
923
+ # It immediately returns a HTTPClient::Connection instance as a result.
924
+ def propfind_async(uri, *args)
925
+ request_async2(:propfind, uri, argument_to_hash(args, :body, :header))
926
+ end
927
+
928
+ # Sends PROPPATCH request in async style. See request_async2 for arguments.
929
+ # It immediately returns a HTTPClient::Connection instance as a result.
930
+ def proppatch_async(uri, *args)
931
+ request_async2(:proppatch, uri, argument_to_hash(args, :body, :header))
932
+ end
933
+
934
+ # Sends TRACE request in async style. See request_async2 for arguments.
935
+ # It immediately returns a HTTPClient::Connection instance as a result.
936
+ def trace_async(uri, *args)
937
+ request_async2(:trace, uri, argument_to_hash(args, :query, :header))
938
+ end
939
+
940
+ # Sends a request in async style. request method creates new Thread for
941
+ # HTTP connection and returns a HTTPClient::Connection instance immediately.
942
+ #
943
+ # Arguments definition is the same as request.
944
+ def request_async(method, uri, query = nil, body = nil, header = {})
945
+ uri = to_resource_url(uri)
946
+ do_request_async(method, uri, query, body, header)
947
+ end
948
+
949
+ # new method that has same signature as 'request'
950
+ def request_async2(method, uri, *args)
951
+ query, body, header = keyword_argument(args, :query, :body, :header)
952
+ if [:post, :put].include?(method)
953
+ body ||= ''
954
+ end
955
+ if method == :propfind
956
+ header ||= PROPFIND_DEFAULT_EXTHEADER
957
+ else
958
+ header ||= {}
959
+ end
960
+ uri = to_resource_url(uri)
961
+ do_request_async(method, uri, query, body, header)
962
+ end
963
+
964
+ # Resets internal session for the given URL. Keep-alive connection for the
965
+ # site (host-port pair) is disconnected if exists.
966
+ def reset(uri)
967
+ uri = to_resource_url(uri)
968
+ @session_manager.reset(uri)
969
+ end
970
+
971
+ # Resets all of internal sessions. Keep-alive connections are disconnected.
972
+ def reset_all
973
+ @session_manager.reset_all
974
+ end
975
+
976
+ private
977
+
978
+ class RetryableResponse < StandardError # :nodoc:
979
+ attr_reader :res
980
+
981
+ def initialize(res = nil)
982
+ @res = res
983
+ end
984
+ end
985
+
986
+ class KeepAliveDisconnected < StandardError # :nodoc:
987
+ attr_reader :sess
988
+ attr_reader :cause
989
+ def initialize(sess = nil, cause = nil)
990
+ super("#{self.class.name}: #{cause ? cause.message : nil}")
991
+ @sess = sess
992
+ @cause = cause
993
+ end
994
+ end
995
+
996
+ def hashy_argument_has_keys(args, *key)
997
+ # if the given arg is a single Hash and...
998
+ args.size == 1 and Hash === args[0] and
999
+ # it has any one of the key
1000
+ key.all? { |e| args[0].key?(e) }
1001
+ end
1002
+
1003
+ def do_request(method, uri, query, body, header, &block)
1004
+ res = nil
1005
+ if HTTP::Message.file?(body)
1006
+ pos = body.pos rescue nil
1007
+ end
1008
+ retry_count = @session_manager.protocol_retry_count
1009
+ proxy = no_proxy?(uri) ? nil : @proxy
1010
+ previous_request = previous_response = nil
1011
+ while retry_count > 0
1012
+ body.pos = pos if pos
1013
+ req = create_request(method, uri, query, body, header)
1014
+ if previous_request
1015
+ # to remember IO positions to read
1016
+ req.http_body.positions = previous_request.http_body.positions
1017
+ end
1018
+ begin
1019
+ protect_keep_alive_disconnected do
1020
+ # TODO: remove Connection.new
1021
+ # We want to delete Connection usage in do_get_block but Newrelic gem depends on it.
1022
+ # https://github.com/newrelic/rpm/blob/master/lib/new_relic/agent/instrumentation/httpclient.rb#L34-L36
1023
+ conn = Connection.new
1024
+ res = do_get_block(req, proxy, conn, &block)
1025
+ # Webmock's do_get_block returns ConditionVariable
1026
+ if !res.respond_to?(:previous)
1027
+ res = conn.pop
1028
+ end
1029
+ end
1030
+ res.previous = previous_response
1031
+ break
1032
+ rescue RetryableResponse => e
1033
+ previous_request = req
1034
+ previous_response = res = e.res
1035
+ retry_count -= 1
1036
+ end
1037
+ end
1038
+ res
1039
+ end
1040
+
1041
+ def do_request_async(method, uri, query, body, header)
1042
+ conn = Connection.new
1043
+ t = Thread.new(conn) { |tconn|
1044
+ begin
1045
+ if HTTP::Message.file?(body)
1046
+ pos = body.pos rescue nil
1047
+ end
1048
+ retry_count = @session_manager.protocol_retry_count
1049
+ proxy = no_proxy?(uri) ? nil : @proxy
1050
+ while retry_count > 0
1051
+ body.pos = pos if pos
1052
+ req = create_request(method, uri, query, body, header)
1053
+ begin
1054
+ protect_keep_alive_disconnected do
1055
+ do_get_stream(req, proxy, tconn)
1056
+ end
1057
+ break
1058
+ rescue RetryableResponse
1059
+ retry_count -= 1
1060
+ end
1061
+ end
1062
+ rescue Exception => e
1063
+ conn.push e
1064
+ end
1065
+ }
1066
+ conn.async_thread = t
1067
+ conn
1068
+ end
1069
+
1070
+ def load_environment
1071
+ # http_proxy
1072
+ if getenv('REQUEST_METHOD')
1073
+ # HTTP_PROXY conflicts with the environment variable usage in CGI where
1074
+ # HTTP_* is used for HTTP header information. Unlike open-uri, we
1075
+ # simply ignore http_proxy in CGI env and use cgi_http_proxy instead.
1076
+ self.proxy = getenv('cgi_http_proxy')
1077
+ else
1078
+ self.proxy = getenv('http_proxy')
1079
+ end
1080
+ # no_proxy
1081
+ self.no_proxy = getenv('no_proxy')
1082
+ end
1083
+
1084
+ def getenv(name)
1085
+ ENV[name.downcase] || ENV[name.upcase]
1086
+ end
1087
+
1088
+ def adapt_block(&block)
1089
+ return block if block.arity == 2
1090
+ proc { |r, str| block.call(str) }
1091
+ end
1092
+
1093
+ def follow_redirect(method, uri, query, body, header, &block)
1094
+ uri = to_resource_url(uri)
1095
+ if block
1096
+ b = adapt_block(&block)
1097
+ filtered_block = proc { |r, str|
1098
+ b.call(r, str) if r.ok?
1099
+ }
1100
+ end
1101
+ if HTTP::Message.file?(body)
1102
+ pos = body.pos rescue nil
1103
+ end
1104
+ retry_number = 0
1105
+ previous = nil
1106
+ request_query = query
1107
+ while retry_number < @follow_redirect_count
1108
+ body.pos = pos if pos
1109
+ res = do_request(method, uri, request_query, body, header, &filtered_block)
1110
+ res.previous = previous
1111
+ if res.redirect?
1112
+ if res.header['location'].empty?
1113
+ raise BadResponseError.new("Missing Location header for redirect", res)
1114
+ end
1115
+ method = :get if res.see_other? # See RFC2616 10.3.4
1116
+ uri = urify(@redirect_uri_callback.call(uri, res))
1117
+ # To avoid duped query parameter. 'location' must include query part.
1118
+ request_query = nil
1119
+ previous = res
1120
+ retry_number += 1
1121
+ else
1122
+ return res
1123
+ end
1124
+ end
1125
+ raise BadResponseError.new("retry count exceeded", res)
1126
+ end
1127
+
1128
+ def success_content(res)
1129
+ if res.ok?
1130
+ return res.content
1131
+ else
1132
+ raise BadResponseError.new("unexpected response: #{res.header.inspect}", res)
1133
+ end
1134
+ end
1135
+
1136
+ def protect_keep_alive_disconnected
1137
+ begin
1138
+ yield
1139
+ rescue KeepAliveDisconnected
1140
+ # Force to create new connection
1141
+ Thread.current[:HTTPClient_AcquireNewConnection] = true
1142
+ begin
1143
+ yield
1144
+ ensure
1145
+ Thread.current[:HTTPClient_AcquireNewConnection] = false
1146
+ end
1147
+ end
1148
+ end
1149
+
1150
+ def create_request(method, uri, query, body, header)
1151
+ method = method.to_s.upcase
1152
+ if header.is_a?(Hash)
1153
+ header = @default_header.merge(header).to_a
1154
+ else
1155
+ header = @default_header.to_a + header.dup
1156
+ end
1157
+ boundary = nil
1158
+ if body
1159
+ _, content_type = header.find { |key, value|
1160
+ key.to_s.downcase == 'content-type'
1161
+ }
1162
+ if content_type
1163
+ if /\Amultipart/ =~ content_type
1164
+ if content_type =~ /boundary=(.+)\z/
1165
+ boundary = $1
1166
+ else
1167
+ boundary = create_boundary
1168
+ content_type = "#{content_type}; boundary=#{boundary}"
1169
+ header = override_header(header, 'content-type', content_type)
1170
+ end
1171
+ end
1172
+ else
1173
+ if file_in_form_data?(body)
1174
+ boundary = create_boundary
1175
+ content_type = "multipart/form-data; boundary=#{boundary}"
1176
+ else
1177
+ content_type = 'application/x-www-form-urlencoded'
1178
+ end
1179
+ header << ['Content-Type', content_type]
1180
+ end
1181
+ end
1182
+ req = HTTP::Message.new_request(method, uri, query, body, boundary)
1183
+ header.each do |key, value|
1184
+ req.header.add(key.to_s, value)
1185
+ end
1186
+ if @cookie_manager
1187
+ cookie_value = @cookie_manager.cookie_value(uri)
1188
+ if cookie_value
1189
+ req.header.add('Cookie', cookie_value)
1190
+ end
1191
+ end
1192
+ req
1193
+ end
1194
+
1195
+ def create_boundary
1196
+ Digest::SHA1.hexdigest(Time.now.to_s)
1197
+ end
1198
+
1199
+ def file_in_form_data?(body)
1200
+ HTTP::Message.multiparam_query?(body) &&
1201
+ body.any? { |k, v| HTTP::Message.file?(v) }
1202
+ end
1203
+
1204
+ def override_header(header, key, value)
1205
+ result = []
1206
+ header.each do |k, v|
1207
+ if k.to_s.downcase == key
1208
+ result << [key, value]
1209
+ else
1210
+ result << [k, v]
1211
+ end
1212
+ end
1213
+ result
1214
+ end
1215
+
1216
+ NO_PROXY_HOSTS = ['localhost']
1217
+
1218
+ def no_proxy?(uri)
1219
+ if !@proxy or NO_PROXY_HOSTS.include?(uri.host)
1220
+ return true
1221
+ end
1222
+ @no_proxy_regexps.each do |regexp, port|
1223
+ if !port || uri.port == port.to_i
1224
+ if regexp =~ uri.host
1225
+ return true
1226
+ end
1227
+ end
1228
+ end
1229
+ false
1230
+ end
1231
+
1232
+ # !! CAUTION !!
1233
+ # Method 'do_get*' runs under MT conditon. Be careful to change.
1234
+ def do_get_block(req, proxy, conn, &block)
1235
+ @request_filter.each do |filter|
1236
+ filter.filter_request(req)
1237
+ end
1238
+ if str = @test_loopback_response.shift
1239
+ dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
1240
+ res = HTTP::Message.new_response(str, req.header)
1241
+ conn.push(res)
1242
+ return res
1243
+ end
1244
+ content = block ? nil : ''
1245
+ res = HTTP::Message.new_response(content, req.header)
1246
+ @debug_dev << "= Request\n\n" if @debug_dev
1247
+ sess = @session_manager.query(req, proxy)
1248
+ res.peer_cert = sess.ssl_peer_cert
1249
+ @debug_dev << "\n\n= Response\n\n" if @debug_dev
1250
+ do_get_header(req, res, sess)
1251
+ conn.push(res)
1252
+ sess.get_body do |part|
1253
+ set_encoding(part, res.body_encoding)
1254
+ if block
1255
+ block.call(res, part.dup)
1256
+ else
1257
+ content << part
1258
+ end
1259
+ end
1260
+ # there could be a race condition but it's OK to cache unreusable
1261
+ # connection because we do retry for that case.
1262
+ @session_manager.keep(sess) unless sess.closed?
1263
+ commands = @request_filter.collect { |filter|
1264
+ filter.filter_response(req, res)
1265
+ }
1266
+ if commands.find { |command| command == :retry }
1267
+ raise RetryableResponse.new(res)
1268
+ end
1269
+ res
1270
+ end
1271
+
1272
+ def do_get_stream(req, proxy, conn)
1273
+ @request_filter.each do |filter|
1274
+ filter.filter_request(req)
1275
+ end
1276
+ if str = @test_loopback_response.shift
1277
+ dump_dummy_request_response(req.http_body.dump, str) if @debug_dev
1278
+ conn.push(HTTP::Message.new_response(StringIO.new(str), req.header))
1279
+ return
1280
+ end
1281
+ piper, pipew = IO.pipe
1282
+ pipew.binmode
1283
+ res = HTTP::Message.new_response(piper, req.header)
1284
+ @debug_dev << "= Request\n\n" if @debug_dev
1285
+ sess = @session_manager.query(req, proxy)
1286
+ res.peer_cert = sess.ssl_peer_cert
1287
+ @debug_dev << "\n\n= Response\n\n" if @debug_dev
1288
+ do_get_header(req, res, sess)
1289
+ conn.push(res)
1290
+ sess.get_body do |part|
1291
+ set_encoding(part, res.body_encoding)
1292
+ pipew.write(part)
1293
+ end
1294
+ pipew.close
1295
+ @session_manager.keep(sess) unless sess.closed?
1296
+ _ = @request_filter.collect { |filter|
1297
+ filter.filter_response(req, res)
1298
+ }
1299
+ # ignore commands (not retryable in async mode)
1300
+ res
1301
+ end
1302
+
1303
+ def do_get_header(req, res, sess)
1304
+ res.http_version, res.status, res.reason, headers = sess.get_header
1305
+ res.header.set_headers(headers)
1306
+ if @cookie_manager
1307
+ res.header['set-cookie'].each do |cookie|
1308
+ @cookie_manager.parse(cookie, req.header.request_uri)
1309
+ end
1310
+ end
1311
+ end
1312
+
1313
+ def dump_dummy_request_response(req, res)
1314
+ @debug_dev << "= Dummy Request\n\n"
1315
+ @debug_dev << req
1316
+ @debug_dev << "\n\n= Dummy Response\n\n"
1317
+ @debug_dev << res
1318
+ end
1319
+
1320
+ def set_encoding(str, encoding)
1321
+ str.force_encoding(encoding) if encoding
1322
+ end
1323
+
1324
+ def to_resource_url(uri)
1325
+ u = urify(uri)
1326
+ if @base_url && u.scheme.nil? && u.host.nil?
1327
+ urify(@base_url) + uri
1328
+ else
1329
+ u
1330
+ end
1331
+ end
1332
+ end