httpclient 2.6.0.1 → 2.8.3

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