httpclient 2.6.0.1 → 2.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/lib/httpclient.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
  #
4
4
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
5
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -149,6 +149,16 @@ require 'httpclient/cookie'
149
149
  # clnt.ssl_config.set_client_cert_file(user_cert_file, user_key_file)
150
150
  # clnt.get(https_url)
151
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
+ #
152
162
  # === Handling Cookies
153
163
  #
154
164
  # 1. Using volatile Cookies. Nothing to do. HTTPClient handles Cookies.
@@ -299,7 +309,6 @@ class HTTPClient
299
309
  if assignable
300
310
  aname = name + '='
301
311
  define_method(aname) { |rhs|
302
- reset_all
303
312
  @session_manager.__send__(aname, rhs)
304
313
  }
305
314
  end
@@ -326,7 +335,7 @@ class HTTPClient
326
335
  attr_accessor :follow_redirect_count
327
336
  # Base url of resources.
328
337
  attr_accessor :base_url
329
- # Defalut request header.
338
+ # Default request header.
330
339
  attr_accessor :default_header
331
340
 
332
341
  # Set HTTP version as a String:: 'HTTP/1.0' or 'HTTP/1.1'
@@ -346,6 +355,8 @@ class HTTPClient
346
355
  # if your ruby is older than 2005-09-06, do not set socket_sync = false to
347
356
  # avoid an SSL socket blocking bug in openssl/buffering.rb.
348
357
  attr_proxy(:socket_sync, true)
358
+ # Enables TCP keepalive; no timing settings exist at present
359
+ attr_proxy(:tcp_keepalive, true)
349
360
  # User-Agent header in HTTP request.
350
361
  attr_proxy(:agent_name, true)
351
362
  # From header in HTTP request.
@@ -355,6 +366,9 @@ class HTTPClient
355
366
  attr_proxy(:test_loopback_http_response)
356
367
  # Decompress a compressed (with gzip or deflate) content body transparently. false by default.
357
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)
358
372
  # Local socket address. Set HTTPClient#socket_local.host and HTTPClient#socket_local.port to specify local binding hostname and port of TCP socket.
359
373
  attr_proxy(:socket_local, true)
360
374
 
@@ -383,10 +397,25 @@ class HTTPClient
383
397
  #
384
398
  # After you set :base_url, all resources you pass to get, post and other
385
399
  # methods are recognized to be prefixed with base_url. Say base_url is
386
- # 'https://api.example.com/v1, get('/users') is the same as
400
+ # 'https://api.example.com/v1/, get('users') is the same as
387
401
  # get('https://api.example.com/v1/users') internally. You can also pass
388
402
  # full URL from 'http://' even after setting base_url.
389
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
+ #
390
419
  # :default_header is for providing default headers Hash that all HTTP
391
420
  # requests should have, such as custom 'Authorization' header in API.
392
421
  # You can override :default_header with :header Hash parameter in HTTP
@@ -396,7 +425,7 @@ class HTTPClient
396
425
  # HTTP client must send Authorization header after it gets 401 error
397
426
  # from server from security reason. But in some situation (e.g. API
398
427
  # client) you might want to send Authorization from the beginning.
399
- def initialize(*args)
428
+ def initialize(*args, &block)
400
429
  proxy, agent_name, from, base_url, default_header, force_basic_auth =
401
430
  keyword_argument(args, :proxy, :agent_name, :from, :base_url, :default_header, :force_basic_auth)
402
431
  @proxy = nil # assigned later.
@@ -420,6 +449,7 @@ class HTTPClient
420
449
  load_environment
421
450
  self.proxy = proxy if proxy
422
451
  keep_webmock_compat
452
+ instance_eval(&block) if block
423
453
  end
424
454
 
425
455
  # webmock 1.6.2 depends on HTTP::Message#body.content to work.
@@ -692,9 +722,9 @@ class HTTPClient
692
722
  def default_redirect_uri_callback(uri, res)
693
723
  newuri = urify(res.header['location'][0])
694
724
  if !http?(newuri) && !https?(newuri)
695
- newuri = uri + newuri
696
- warn("could be a relative URI in location header which is not recommended")
725
+ warn("#{newuri}: a relative URI in location header which is not recommended")
697
726
  warn("'The field value consists of a single absolute URI' in HTTP spec")
727
+ newuri = uri + newuri
698
728
  end
699
729
  if https?(uri) && !https?(newuri)
700
730
  raise BadResponseError.new("redirecting to non-https resource")
@@ -713,6 +743,16 @@ class HTTPClient
713
743
  request(:get, uri, argument_to_hash(args, :query, :header, :follow_redirect), &block)
714
744
  end
715
745
 
746
+ # Sends PATCH request to the specified URL. See request for arguments.
747
+ def patch(uri, *args, &block)
748
+ if hashy_argument_has_keys(args, :query, :body)
749
+ new_args = args[0]
750
+ else
751
+ new_args = argument_to_hash(args, :body, :header)
752
+ end
753
+ request(:patch, uri, new_args, &block)
754
+ end
755
+
716
756
  # Sends POST request to the specified URL. See request for arguments.
717
757
  # You should not depend on :follow_redirect => true for POST method. It
718
758
  # sends the same POST method to the new location which is prohibited in HTTP spec.
@@ -808,13 +848,7 @@ class HTTPClient
808
848
  end
809
849
  uri = to_resource_url(uri)
810
850
  if block
811
- if block.arity == 1
812
- filtered_block = proc { |res, str|
813
- block.call(str)
814
- }
815
- else
816
- filtered_block = block
817
- end
851
+ filtered_block = adapt_block(&block)
818
852
  end
819
853
  if follow_redirect
820
854
  follow_redirect(method, uri, query, body, header, &block)
@@ -835,6 +869,17 @@ class HTTPClient
835
869
  request_async2(:get, uri, argument_to_hash(args, :query, :header))
836
870
  end
837
871
 
872
+ # Sends PATCH request in async style. See request_async2 for arguments.
873
+ # It immediately returns a HTTPClient::Connection instance as a result.
874
+ def patch_async(uri, *args)
875
+ if hashy_argument_has_keys(args, :query, :body)
876
+ new_args = args[0]
877
+ else
878
+ new_args = argument_to_hash(args, :body, :header)
879
+ end
880
+ request_async2(:patch, uri, new_args)
881
+ end
882
+
838
883
  # Sends POST request in async style. See request_async for arguments.
839
884
  # It immediately returns a HTTPClient::Connection instance as a result.
840
885
  def post_async(uri, *args)
@@ -1035,11 +1080,17 @@ private
1035
1080
  ENV[name.downcase] || ENV[name.upcase]
1036
1081
  end
1037
1082
 
1083
+ def adapt_block(&block)
1084
+ return block if block.arity == 2
1085
+ proc { |r, str| block.call(str) }
1086
+ end
1087
+
1038
1088
  def follow_redirect(method, uri, query, body, header, &block)
1039
1089
  uri = to_resource_url(uri)
1040
1090
  if block
1091
+ b = adapt_block(&block)
1041
1092
  filtered_block = proc { |r, str|
1042
- block.call(str) if r.ok?
1093
+ b.call(r, str) if r.ok?
1043
1094
  }
1044
1095
  end
1045
1096
  if HTTP::Message.file?(body)
@@ -1080,7 +1131,7 @@ private
1080
1131
  def protect_keep_alive_disconnected
1081
1132
  begin
1082
1133
  yield
1083
- rescue KeepAliveDisconnected => e
1134
+ rescue KeepAliveDisconnected
1084
1135
  # Force to create new connection
1085
1136
  Thread.current[:HTTPClient_AcquireNewConnection] = true
1086
1137
  begin
@@ -1101,7 +1152,7 @@ private
1101
1152
  boundary = nil
1102
1153
  if body
1103
1154
  _, content_type = header.find { |key, value|
1104
- key.downcase == 'content-type'
1155
+ key.to_s.downcase == 'content-type'
1105
1156
  }
1106
1157
  if content_type
1107
1158
  if /\Amultipart/ =~ content_type
@@ -1110,7 +1161,7 @@ private
1110
1161
  else
1111
1162
  boundary = create_boundary
1112
1163
  content_type = "#{content_type}; boundary=#{boundary}"
1113
- header = override_header(header, 'Content-Type', content_type)
1164
+ header = override_header(header, 'content-type', content_type)
1114
1165
  end
1115
1166
  end
1116
1167
  else
@@ -1148,7 +1199,7 @@ private
1148
1199
  def override_header(header, key, value)
1149
1200
  result = []
1150
1201
  header.each do |k, v|
1151
- if k.downcase == key.downcase
1202
+ if k.to_s.downcase == key
1152
1203
  result << [key, value]
1153
1204
  else
1154
1205
  result << [k, v]
@@ -1223,6 +1274,7 @@ private
1223
1274
  return
1224
1275
  end
1225
1276
  piper, pipew = IO.pipe
1277
+ pipew.binmode
1226
1278
  res = HTTP::Message.new_response(piper, req.header)
1227
1279
  @debug_dev << "= Request\n\n" if @debug_dev
1228
1280
  sess = @session_manager.query(req, proxy)
@@ -1267,7 +1319,7 @@ private
1267
1319
  def to_resource_url(uri)
1268
1320
  u = urify(uri)
1269
1321
  if @base_url && u.scheme.nil? && u.host.nil?
1270
- urify(@base_url + uri)
1322
+ urify(@base_url) + uri
1271
1323
  else
1272
1324
  u
1273
1325
  end
data/lib/oauthclient.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # HTTPClient - HTTP client library.
2
- # Copyright (C) 2000-2009 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
2
+ # Copyright (C) 2000-2015 NAKAMURA, Hiroshi <nahi@ruby-lang.org>.
3
3
  #
4
4
  # This program is copyrighted free software by NAKAMURA, Hiroshi. You can
5
5
  # redistribute it and/or modify it under the same terms of Ruby's license;
@@ -33,6 +33,7 @@ class OAuthClient < HTTPClient
33
33
  @oauth_config = HTTPClient::OAuth::Config.new
34
34
  self.www_auth.oauth.set_config(nil, @oauth_config)
35
35
  self.www_auth.oauth.challenge(nil)
36
+ self.strict_response_size_check = true
36
37
  end
37
38
 
38
39
  # Get request token.
data/test/helper.rb CHANGED
@@ -59,11 +59,13 @@ module Helper
59
59
  @client = HTTPClient.new
60
60
  end
61
61
 
62
- #def setup_server
63
- # override it
64
- # @server = WEBrick::HTTPServer.new(...)
65
- # @server_thread = start_server_thread(@server)
66
- #end
62
+ def escape_noproxy
63
+ backup = HTTPClient::NO_PROXY_HOSTS.dup
64
+ HTTPClient::NO_PROXY_HOSTS.clear
65
+ yield
66
+ ensure
67
+ HTTPClient::NO_PROXY_HOSTS.replace(backup)
68
+ end
67
69
 
68
70
  def setup_proxyserver
69
71
  @proxyserver = WEBrick::HTTPProxyServer.new(
@@ -0,0 +1,32 @@
1
+ require File.expand_path('helper', File.join(File.dirname(__FILE__), ".."))
2
+
3
+
4
+ class PEMUtilsTest < Test::Unit::TestCase
5
+ include Helper
6
+
7
+ def setup
8
+ @raw_cert = "-----BEGIN CERTIFICATE-----\nMIIDOTCCAiGgAwIBAgIBAjANBgkqhkiG9w0BAQsFADBCMRMwEQYKCZImiZPyLGQB\nGRYDb3JnMRkwFwYKCZImiZPyLGQBGRYJcnVieS1sYW5nMRAwDgYDVQQDDAdSdWJ5\nIENBMB4XDTE2MDgxMDE3MjEzNFoXDTE3MDgxMDE3MjEzNFowSzETMBEGCgmSJomT\n8ixkARkWA29yZzEZMBcGCgmSJomT8ixkARkWCXJ1YnktbGFuZzEZMBcGA1UEAwwQ\nUnVieSBjZXJ0aWZpY2F0ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB\nAJCfsSXpSMpmZCVa+ZCM+QDgomnhDlvnrGDq6pasTaIspGTXgws+7r8Dt/cNe6EH\nHJpRH2cGRiO4yPcfcT9eS4X7k8OC4f33wHfACOmLu6LeoNE8ujmSk6L6WzLUI+sE\nnLZbFrXxoAo4XHsm8vEG9C+jEoXZ1p+47wrAGaDwDQTnzlMy4dT9pRQEJP2G/Rry\nUkuZn8SUWmh3/YS78iaSzsNF1cgE1ealHOrPPFDjiCGDaH/LHyUPYlbFSLZ/B7Qx\nLxi5sePLcywWq/EJrmWpgeVTDjtNijsdKv/A3qkY+fm/oD0pzt7XsfJaP9YKNyJO\nQFdxWZeiPcDF+Hwf+IwSr+kCAwEAAaMxMC8wDgYDVR0PAQH/BAQDAgeAMB0GA1Ud\nDgQWBBQNvzYzJyXemGhxbA8NMXLolDnPyjANBgkqhkiG9w0BAQsFAAOCAQEARIJV\noKejGlOTn71QutnNnu07UtTu0IHs6YqjYzzND+m4JXLN+wvYm72AFUG0b1L7dRg0\niK8XjQrlNQNVqP1Mc6tffchy20neOPOHeiO6qTdRU8P2S8D3Uwe+1qhgxjfE+cWc\nwZmWxYK4HA8c58PxWMqrkr2QqXDplG9KWLvOgrtPGiLLZcQSKhvvB63QzItHBDU6\nRayiJY3oPkK/HrIvFlySqFqzWmuyknkciOFywEHQMz/tcSFJ2QFpPj/tBz9VXohH\nZ8KscmfhZrTPBjo+ky1lz/WraWoz4LMiLnkC2ABczWLRSawu+v3Irx1NFJngt05e\npqwtqIUeg7j+JLiTaA==\n-----END CERTIFICATE-----"
9
+ end
10
+
11
+ def test_read_certificate
12
+ assert_nothing_raised do
13
+ binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(@raw_cert)
14
+ end
15
+ end
16
+
17
+ def test_read_certificate_works_with_random_ascii_text_outside_begin_end
18
+ raw_cert_with_ascii = "some text before begin\n" + @raw_cert + "\nsome text after end"
19
+ assert_nothing_raised do
20
+ binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(raw_cert_with_ascii)
21
+ end
22
+ end
23
+
24
+ def test_read_certificate_uses_all_content_if_missing_begin_end
25
+ cert = @raw_cert.sub(/-----BEGIN CERTIFICATE-----/, '').sub(/-----END CERTIFICATE-----/, '')
26
+ assert_nothing_raised do
27
+ binary = HTTPClient::JRubySSLSocket::PEMUtils.read_certificate(cert)
28
+ end
29
+ end
30
+
31
+
32
+ end
data/test/test_auth.rb CHANGED
@@ -2,7 +2,6 @@ require File.expand_path('helper', File.dirname(__FILE__))
2
2
  require 'digest/md5'
3
3
  require 'rack'
4
4
  require 'rack/lint'
5
- require 'rack/showexceptions'
6
5
  require 'rack-ntlm'
7
6
 
8
7
  class TestAuth < Test::Unit::TestCase
@@ -124,6 +123,7 @@ class TestAuth < Test::Unit::TestCase
124
123
  end
125
124
  # Make it work if @value == nil
126
125
  class SecurityBuffer < FieldSet
126
+ remove_method(:data_size) if method_defined?(:data_size)
127
127
  def data_size
128
128
  @active && @value ? @value.size : 0
129
129
  end
@@ -230,7 +230,7 @@ class TestAuth < Test::Unit::TestCase
230
230
  c.www_auth.basic_auth.instance_eval { @scheme = "BASIC" }
231
231
  c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
232
232
 
233
- threads = 100.times.map { |idx|
233
+ 100.times.map { |idx|
234
234
  Thread.new(idx) { |idx2|
235
235
  Thread.abort_on_exception = true
236
236
  Thread.pass
@@ -251,7 +251,7 @@ class TestAuth < Test::Unit::TestCase
251
251
  c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
252
252
  c.debug_dev = str = ''
253
253
  c.get_content("http://localhost:#{serverport}/basic_auth/sub/dir/")
254
- assert_match /Authorization: Basic YWRtaW46YWRtaW4=/, str
254
+ assert_match(/Authorization: Basic YWRtaW46YWRtaW4=/, str)
255
255
  end
256
256
 
257
257
  def test_digest_auth
@@ -267,7 +267,7 @@ class TestAuth < Test::Unit::TestCase
267
267
  c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
268
268
  c.debug_dev = str = ''
269
269
  c.get_content("http://localhost:#{serverport}/digest_auth/sub/dir/")
270
- assert_match /Authorization: Digest/, str
270
+ assert_match(/Authorization: Digest/, str)
271
271
  end
272
272
 
273
273
  def test_digest_auth_with_block
@@ -332,6 +332,16 @@ class TestAuth < Test::Unit::TestCase
332
332
  assert_match(/Proxy-Authorization: Basic YWRtaW46YWRtaW4=/, str)
333
333
  end
334
334
 
335
+ def test_proxy_auth_force
336
+ c = HTTPClient.new
337
+ c.set_proxy_auth('admin', 'admin')
338
+ c.force_basic_auth = true
339
+ c.test_loopback_http_response << "HTTP/1.0 200 OK\nContent-Length: 2\n\nOK"
340
+ c.debug_dev = str = ''
341
+ c.get_content('http://example.com/')
342
+ assert_match(/Proxy-Authorization: Basic YWRtaW46YWRtaW4=/, str)
343
+ end
344
+
335
345
  def test_proxy_auth_reuses_credentials
336
346
  c = HTTPClient.new
337
347
  c.set_proxy_auth('admin', 'admin')
@@ -442,11 +452,20 @@ class TestAuth < Test::Unit::TestCase
442
452
  end
443
453
 
444
454
  def test_basic_auth_post_with_multipart
445
- c = HTTPClient.new
446
- c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
447
- File.open(__FILE__) do |f|
448
- # read 'f' twice for authorization negotiation
449
- assert_equal('basic_auth OK', c.post("http://localhost:#{serverport}/basic_auth", :file => f).content)
455
+ retry_times = 0
456
+ begin
457
+ c = HTTPClient.new
458
+ c.set_auth("http://localhost:#{serverport}/", 'admin', 'admin')
459
+ File.open(__FILE__) do |f|
460
+ # read 'f' twice for authorization negotiation
461
+ assert_equal('basic_auth OK', c.post("http://localhost:#{serverport}/basic_auth", :file => f).content)
462
+ end
463
+ rescue Errno::ECONNRESET, HTTPClient::KeepAliveDisconnected
464
+ # TODO: WEBrick server returns ECONNRESET/EPIPE before sending Unauthorized response to client?
465
+ raise if retry_times > 5
466
+ retry_times += 1
467
+ sleep 1
468
+ retry
450
469
  end
451
470
  end
452
471
 
data/test/test_cookie.rb CHANGED
@@ -290,8 +290,8 @@ EOF
290
290
  def test_load_cookies_escaped
291
291
  uri = urify('http://example.org/')
292
292
  f = Tempfile.new('test_cookie')
293
- File.open(f.path, 'w') do |f|
294
- f.write <<EOF
293
+ File.open(f.path, 'w') do |out|
294
+ out.write <<EOF
295
295
  http://example.org/ key1 "value" 0 .example.org / 13 0
296
296
  http://example.org/ key2 "" 0 .example.org / 13 0
297
297
  http://example.org/ key3 0 .example.org / 13 0
@@ -347,11 +347,13 @@ class TestClient < Test::Unit::TestCase
347
347
  def test_receive_timeout
348
348
  # this test takes 2 sec
349
349
  assert_equal('hello', @client.get_content(serverurl + 'sleep?sec=2'))
350
+ @client.reset_all
350
351
  @client.receive_timeout = 1
351
352
  assert_equal('hello', @client.get_content(serverurl + 'sleep?sec=0'))
352
353
  assert_raise(HTTPClient::ReceiveTimeoutError) do
353
354
  @client.get_content(serverurl + 'sleep?sec=2')
354
355
  end
356
+ @client.reset_all
355
357
  @client.receive_timeout = 3
356
358
  assert_equal('hello', @client.get_content(serverurl + 'sleep?sec=2'))
357
359
  end