httpclient-fixcerts 2.8.5

Sign up to get free protection for your applications and to get access to all the features.
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