ssl_certifier 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,4 @@
1
+ module SslCertifier
2
+ require 'open-uri'
3
+ require 'ssl_certifier/open-uri.rb'
4
+ end
@@ -0,0 +1,12 @@
1
+ module OpenURI
2
+ CaCertOptions = {:ssl_ca_cert => File.join(File.expand_path("../../../certs/", __FILE__), 'cacert.pem')}
3
+
4
+ class << self
5
+ alias_method :open_http_without_ca_cert, :open_http
6
+
7
+ def OpenURI.open_http(buf, target, proxy, options) # :nodoc:
8
+ options = CaCertOptions.merge(options)
9
+ OpenURI.open_http_without_ca_cert(buf, target, proxy, options)
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,3 @@
1
+ module SslCertifier
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,59 @@
1
+ Certificate:
2
+ Data:
3
+ Version: 3 (0x2)
4
+ Serial Number: 0 (0x0)
5
+ Signature Algorithm: sha1WithRSAEncryption
6
+ Issuer: C=JP, ST=Tokyo, O=RubyTest, CN=Ruby Test CA
7
+ Validity
8
+ Not Before: Jan 1 00:00:00 2009 GMT
9
+ Not After : Dec 31 23:59:59 2049 GMT
10
+ Subject: C=JP, ST=Tokyo, O=RubyTest, CN=Ruby Test CA
11
+ Subject Public Key Info:
12
+ Public Key Algorithm: rsaEncryption
13
+ RSA Public Key: (1024 bit)
14
+ Modulus (1024 bit):
15
+ 00:9f:58:19:39:bc:ea:0c:b8:c3:5d:12:a7:d8:20:
16
+ 6c:53:ac:91:34:c8:b4:db:3f:56:f6:75:b6:6c:23:
17
+ 80:23:6a:5f:b3:f6:9a:3e:00:b4:16:19:1c:9c:2c:
18
+ 8d:e8:53:d5:0b:f1:52:3f:7b:60:93:86:ae:89:ab:
19
+ 20:82:9a:b6:72:14:3c:4d:a9:0b:6c:34:79:9e:d3:
20
+ 14:82:6d:c9:3b:90:d9:5e:68:6f:8c:b5:d8:09:f4:
21
+ 6f:3b:22:9f:5e:81:9c:37:df:cf:90:36:65:57:dc:
22
+ ad:31:ca:8b:48:92:a7:3c:1e:42:e9:1c:4e:1e:cb:
23
+ 36:c1:44:4e:ab:9a:b2:73:6d
24
+ Exponent: 65537 (0x10001)
25
+ X509v3 extensions:
26
+ X509v3 Basic Constraints:
27
+ CA:FALSE
28
+ Netscape Comment:
29
+ OpenSSL Generated Certificate
30
+ X509v3 Subject Key Identifier:
31
+ 24:6F:03:A3:EE:06:51:75:B2:BA:FC:3A:38:59:BF:ED:87:CD:E8:7F
32
+ X509v3 Authority Key Identifier:
33
+ keyid:24:6F:03:A3:EE:06:51:75:B2:BA:FC:3A:38:59:BF:ED:87:CD:E8:7F
34
+
35
+ Signature Algorithm: sha1WithRSAEncryption
36
+ 13:eb:db:ca:cd:90:f2:09:9e:d9:72:70:5e:42:5d:11:84:ce:
37
+ 00:1d:c4:2f:41:d2:3e:16:e5:d4:97:1f:43:a9:a7:9c:fa:60:
38
+ c4:35:96:f2:f6:0d:13:6d:0f:36:dd:59:03:08:ee:2e:a6:df:
39
+ 9e:d8:6d:ca:72:8f:02:c2:2b:53:7b:12:7f:55:81:6c:9e:7d:
40
+ e7:40:7e:f8:f5:75:0d:4b:a0:8d:ee:a4:d9:e8:5f:06:c9:86:
41
+ 66:71:70:6c:41:81:6a:dd:a4:4f:a3:c1:ac:70:d4:78:1b:23:
42
+ 30:2f:a5:ef:98:ee:d4:62:80:fd:bf:d4:7a:9b:8e:2d:18:e5:
43
+ 00:46
44
+ -----BEGIN CERTIFICATE-----
45
+ MIICfzCCAeigAwIBAgIBADANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJKUDEO
46
+ MAwGA1UECBMFVG9reW8xETAPBgNVBAoTCFJ1YnlUZXN0MRUwEwYDVQQDEwxSdWJ5
47
+ IFRlc3QgQ0EwHhcNMDkwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjBHMQswCQYD
48
+ VQQGEwJKUDEOMAwGA1UECBMFVG9reW8xETAPBgNVBAoTCFJ1YnlUZXN0MRUwEwYD
49
+ VQQDEwxSdWJ5IFRlc3QgQ0EwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAJ9Y
50
+ GTm86gy4w10Sp9ggbFOskTTItNs/VvZ1tmwjgCNqX7P2mj4AtBYZHJwsjehT1Qvx
51
+ Uj97YJOGromrIIKatnIUPE2pC2w0eZ7TFIJtyTuQ2V5ob4y12An0bzsin16BnDff
52
+ z5A2ZVfcrTHKi0iSpzweQukcTh7LNsFETquasnNtAgMBAAGjezB5MAkGA1UdEwQC
53
+ MAAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2VuZXJhdGVkIENlcnRpZmljYXRl
54
+ MB0GA1UdDgQWBBQkbwOj7gZRdbK6/Do4Wb/th83ofzAfBgNVHSMEGDAWgBQkbwOj
55
+ 7gZRdbK6/Do4Wb/th83ofzANBgkqhkiG9w0BAQUFAAOBgQAT69vKzZDyCZ7ZcnBe
56
+ Ql0RhM4AHcQvQdI+FuXUlx9Dqaec+mDENZby9g0TbQ823VkDCO4upt+e2G3Kco8C
57
+ witTexJ/VYFsnn3nQH749XUNS6CN7qTZ6F8GyYZmcXBsQYFq3aRPo8GscNR4GyMw
58
+ L6XvmO7UYoD9v9R6m44tGOUARg==
59
+ -----END CERTIFICATE-----
@@ -0,0 +1,61 @@
1
+ Certificate:
2
+ Data:
3
+ Version: 3 (0x2)
4
+ Serial Number: 1 (0x1)
5
+ Signature Algorithm: sha1WithRSAEncryption
6
+ Issuer: C=JP, ST=Tokyo, O=RubyTest, CN=Ruby Test CA
7
+ Validity
8
+ Not Before: Jan 1 00:00:00 2009 GMT
9
+ Not After : Dec 31 23:59:59 2049 GMT
10
+ Subject: C=JP, ST=Tokyo, O=RubyTest, CN=127.0.0.1
11
+ Subject Public Key Info:
12
+ Public Key Algorithm: rsaEncryption
13
+ RSA Public Key: (1024 bit)
14
+ Modulus (1024 bit):
15
+ 00:bb:bd:74:69:53:58:50:24:79:f2:eb:db:8b:97:
16
+ e4:69:a4:dd:48:0c:40:35:62:42:b3:35:8c:96:2a:
17
+ 62:76:98:b5:2a:e0:f8:78:33:b6:ff:f8:55:bf:44:
18
+ 69:21:d7:b5:0e:bd:8a:dd:31:1b:88:d5:b4:5e:7a:
19
+ 82:e0:ba:99:6c:04:76:e9:ff:e6:f8:f5:06:8e:7e:
20
+ a4:db:db:eb:43:44:12:a7:ca:ca:2b:aa:5f:83:10:
21
+ e2:9e:35:55:e8:e8:af:be:c8:7d:bb:c2:d4:aa:c1:
22
+ 1c:57:0b:c0:0c:3a:1d:6e:23:a9:03:26:7c:ea:8c:
23
+ f0:86:61:ce:f1:ff:42:c7:23
24
+ Exponent: 65537 (0x10001)
25
+ X509v3 extensions:
26
+ X509v3 Basic Constraints:
27
+ CA:FALSE
28
+ Netscape Cert Type:
29
+ SSL Server
30
+ Netscape Comment:
31
+ OpenSSL Generated Certificate
32
+ X509v3 Subject Key Identifier:
33
+ 7F:17:5A:58:88:96:E1:1F:44:EA:FF:AD:C6:2E:90:E2:95:32:DD:F0
34
+ X509v3 Authority Key Identifier:
35
+ keyid:24:6F:03:A3:EE:06:51:75:B2:BA:FC:3A:38:59:BF:ED:87:CD:E8:7F
36
+
37
+ Signature Algorithm: sha1WithRSAEncryption
38
+ 9a:34:99:ea:76:a2:ed:f0:f7:a7:75:3b:81:fb:75:57:93:c1:
39
+ 27:b6:1e:7a:38:67:95:be:58:42:9a:0a:dd:2b:23:fb:85:42:
40
+ 80:34:bf:b9:0e:9c:5e:5a:dc:2d:25:8c:68:02:a2:c7:7f:c0:
41
+ eb:f3:e0:61:e2:05:e5:7e:c1:e0:33:1c:76:65:23:2c:25:08:
42
+ f6:5a:11:b9:d4:f7:e3:80:bb:b0:ce:76:1a:56:22:af:e2:4a:
43
+ e1:7e:a4:60:f3:fd:9c:53:46:51:57:32:6b:05:53:80:5c:a5:
44
+ 61:93:87:ae:06:a8:a2:ba:4d:a1:b7:1b:0f:8f:82:0a:e8:b3:
45
+ ea:63
46
+ -----BEGIN CERTIFICATE-----
47
+ MIICkTCCAfqgAwIBAgIBATANBgkqhkiG9w0BAQUFADBHMQswCQYDVQQGEwJKUDEO
48
+ MAwGA1UECBMFVG9reW8xETAPBgNVBAoTCFJ1YnlUZXN0MRUwEwYDVQQDEwxSdWJ5
49
+ IFRlc3QgQ0EwHhcNMDkwMTAxMDAwMDAwWhcNNDkxMjMxMjM1OTU5WjBEMQswCQYD
50
+ VQQGEwJKUDEOMAwGA1UECBMFVG9reW8xETAPBgNVBAoTCFJ1YnlUZXN0MRIwEAYD
51
+ VQQDEwkxMjcuMC4wLjEwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALu9dGlT
52
+ WFAkefLr24uX5Gmk3UgMQDViQrM1jJYqYnaYtSrg+Hgztv/4Vb9EaSHXtQ69it0x
53
+ G4jVtF56guC6mWwEdun/5vj1Bo5+pNvb60NEEqfKyiuqX4MQ4p41Vejor77IfbvC
54
+ 1KrBHFcLwAw6HW4jqQMmfOqM8IZhzvH/QscjAgMBAAGjgY8wgYwwCQYDVR0TBAIw
55
+ ADARBglghkgBhvhCAQEEBAMCBkAwLAYJYIZIAYb4QgENBB8WHU9wZW5TU0wgR2Vu
56
+ ZXJhdGVkIENlcnRpZmljYXRlMB0GA1UdDgQWBBR/F1pYiJbhH0Tq/63GLpDilTLd
57
+ 8DAfBgNVHSMEGDAWgBQkbwOj7gZRdbK6/Do4Wb/th83ofzANBgkqhkiG9w0BAQUF
58
+ AAOBgQCaNJnqdqLt8PendTuB+3VXk8Enth56OGeVvlhCmgrdKyP7hUKANL+5Dpxe
59
+ WtwtJYxoAqLHf8Dr8+Bh4gXlfsHgMxx2ZSMsJQj2WhG51PfjgLuwznYaViKv4krh
60
+ fqRg8/2cU0ZRVzJrBVOAXKVhk4euBqiiuk2htxsPj4IK6LPqYw==
61
+ -----END CERTIFICATE-----
@@ -0,0 +1,67 @@
1
+ Private-Key: (1024 bit)
2
+ modulus:
3
+ 00:bb:bd:74:69:53:58:50:24:79:f2:eb:db:8b:97:
4
+ e4:69:a4:dd:48:0c:40:35:62:42:b3:35:8c:96:2a:
5
+ 62:76:98:b5:2a:e0:f8:78:33:b6:ff:f8:55:bf:44:
6
+ 69:21:d7:b5:0e:bd:8a:dd:31:1b:88:d5:b4:5e:7a:
7
+ 82:e0:ba:99:6c:04:76:e9:ff:e6:f8:f5:06:8e:7e:
8
+ a4:db:db:eb:43:44:12:a7:ca:ca:2b:aa:5f:83:10:
9
+ e2:9e:35:55:e8:e8:af:be:c8:7d:bb:c2:d4:aa:c1:
10
+ 1c:57:0b:c0:0c:3a:1d:6e:23:a9:03:26:7c:ea:8c:
11
+ f0:86:61:ce:f1:ff:42:c7:23
12
+ publicExponent: 65537 (0x10001)
13
+ privateExponent:
14
+ 00:af:3a:ec:17:0a:f5:d9:07:d2:d3:4c:15:c5:3b:
15
+ 66:b4:bc:6e:d5:ba:a9:8b:aa:45:3b:63:f5:ee:8b:
16
+ 6d:0f:e9:04:e0:1a:cf:8f:d2:25:32:d1:a5:a7:3a:
17
+ c1:2e:17:5a:25:82:00:c4:e7:fb:1d:42:ea:71:6c:
18
+ c4:0f:e1:db:23:ff:1e:d6:c8:d6:60:ca:2d:06:fc:
19
+ 54:3c:03:d4:09:96:bb:38:7a:22:a1:61:2c:f7:d0:
20
+ d0:90:6c:9f:61:ba:61:30:5a:aa:64:ad:43:3a:53:
21
+ 38:e8:ba:cc:8c:51:3e:68:3e:3a:6a:0f:5d:5d:e0:
22
+ d6:df:f2:54:93:d3:14:22:a1
23
+ prime1:
24
+ 00:e8:ec:11:fe:e6:2b:23:21:29:d5:40:a6:11:ec:
25
+ 4c:ae:4d:08:2a:71:18:ac:d1:3e:40:2f:12:41:59:
26
+ 12:09:e2:f7:c2:d7:6b:0a:96:0a:06:e3:90:6a:4e:
27
+ b2:eb:25:b7:09:68:e9:13:ab:d0:5a:29:7a:e4:72:
28
+ 1a:ee:46:a0:8b
29
+ prime2:
30
+ 00:ce:57:5e:31:e9:c9:a8:5b:1f:55:af:67:e2:49:
31
+ 2a:af:90:b6:02:c0:32:2f:ca:ae:1e:de:47:81:73:
32
+ a8:f8:37:53:70:93:24:62:77:d4:b8:80:30:9f:65:
33
+ 26:20:46:ae:5a:65:6e:6d:af:68:4c:8d:e8:3c:f3:
34
+ d1:d1:d9:6e:c9
35
+ exponent1:
36
+ 03:f1:02:b8:f2:82:26:5d:08:4d:30:83:de:e7:c5:
37
+ c0:69:53:4b:0c:90:e3:53:c3:1e:e8:ed:01:28:15:
38
+ b3:0f:21:2c:2d:e3:04:d1:d7:27:98:b0:37:ec:4f:
39
+ 00:c5:a9:9c:42:27:37:8a:ff:c2:96:d3:1a:8c:87:
40
+ c2:22:75:d3
41
+ exponent2:
42
+ 6f:17:32:ab:84:c7:01:51:2d:e9:9f:ea:3a:36:52:
43
+ 38:fb:9c:42:96:df:6e:43:9c:c3:19:c1:3d:bc:db:
44
+ 77:e7:b1:90:a6:67:ac:6b:ff:a6:e5:bd:47:d3:d9:
45
+ 56:ff:36:d7:8c:4c:8b:d9:28:3a:2f:1c:9d:d4:57:
46
+ 5e:b7:c5:a1
47
+ coefficient:
48
+ 45:50:47:66:56:e9:21:d9:40:0e:af:3f:f2:05:77:
49
+ ab:e7:08:40:97:88:2a:51:b3:7e:86:b0:b2:03:2e:
50
+ 6d:36:3f:46:42:97:7d:5a:a2:93:6c:05:c2:8b:8b:
51
+ 2d:af:d5:7d:75:e9:70:f0:2d:21:e3:b9:cf:4d:9a:
52
+ c4:97:e2:79
53
+ -----BEGIN RSA PRIVATE KEY-----
54
+ MIICXAIBAAKBgQC7vXRpU1hQJHny69uLl+RppN1IDEA1YkKzNYyWKmJ2mLUq4Ph4
55
+ M7b/+FW/RGkh17UOvYrdMRuI1bReeoLguplsBHbp/+b49QaOfqTb2+tDRBKnysor
56
+ ql+DEOKeNVXo6K++yH27wtSqwRxXC8AMOh1uI6kDJnzqjPCGYc7x/0LHIwIDAQAB
57
+ AoGBAK867BcK9dkH0tNMFcU7ZrS8btW6qYuqRTtj9e6LbQ/pBOAaz4/SJTLRpac6
58
+ wS4XWiWCAMTn+x1C6nFsxA/h2yP/HtbI1mDKLQb8VDwD1AmWuzh6IqFhLPfQ0JBs
59
+ n2G6YTBaqmStQzpTOOi6zIxRPmg+OmoPXV3g1t/yVJPTFCKhAkEA6OwR/uYrIyEp
60
+ 1UCmEexMrk0IKnEYrNE+QC8SQVkSCeL3wtdrCpYKBuOQak6y6yW3CWjpE6vQWil6
61
+ 5HIa7kagiwJBAM5XXjHpyahbH1WvZ+JJKq+QtgLAMi/Krh7eR4FzqPg3U3CTJGJ3
62
+ 1LiAMJ9lJiBGrlplbm2vaEyN6Dzz0dHZbskCQAPxArjygiZdCE0wg97nxcBpU0sM
63
+ kONTwx7o7QEoFbMPISwt4wTR1yeYsDfsTwDFqZxCJzeK/8KW0xqMh8IiddMCQG8X
64
+ MquExwFRLemf6jo2Ujj7nEKW325DnMMZwT2823fnsZCmZ6xr/6blvUfT2Vb/NteM
65
+ TIvZKDovHJ3UV163xaECQEVQR2ZW6SHZQA6vP/IFd6vnCECXiCpRs36GsLIDLm02
66
+ P0ZCl31aopNsBcKLiy2v1X116XDwLSHjuc9NmsSX4nk=
67
+ -----END RSA PRIVATE KEY-----
data/spec/data.txt ADDED
@@ -0,0 +1 @@
1
+ ddd
@@ -0,0 +1,697 @@
1
+ require 'spec_helper'
2
+
3
+ describe OpenURI do
4
+ context "normal http operations" do
5
+ before(:each) do
6
+ @proxies = %w[http_proxy HTTP_PROXY ftp_proxy FTP_PROXY no_proxy]
7
+ @old_proxies = @proxies.map {|k| ENV[k] }
8
+ @proxies.each {|k| ENV[k] = nil }
9
+ end
10
+
11
+ after(:each) do
12
+ @proxies.each_with_index {|k, i| ENV[k] = @old_proxies[i] }
13
+ end
14
+
15
+ it 'should return 200' do
16
+ with_http do |srv, dr, url|
17
+ open("#{dr}/foo200", "w") {|f| f << "foo200" }
18
+ open("#{url}/foo200") do |f|
19
+ f.status[0].should == "200"
20
+ f.read.should == "foo200"
21
+ end
22
+ end
23
+ end
24
+
25
+ it 'should return 200 for a big file' do
26
+ with_http do |srv, dr, url|
27
+ content = "foo200big"*10240
28
+ open("#{dr}/foo200big", "w") {|f| f << content }
29
+ open("#{url}/foo200big") do |f|
30
+ f.status[0].should == "200"
31
+ f.read.should == content
32
+ end
33
+ end
34
+ end
35
+
36
+ it 'should return 404 for a bad url' do
37
+ with_http do |srv, dr, url|
38
+ lambda { open("#{url}/not-exist") {} }.should raise_error(OpenURI::HTTPError)
39
+ #TODO: somehow get the status from the lambda
40
+ #exc.io.status[0].should == "404"
41
+ end
42
+ end
43
+
44
+ it 'should read the string "foo_ou"' do
45
+ with_http do |srv, dr, url|
46
+ open("#{dr}/foo_ou", "w") {|f| f << "foo_ou" }
47
+ u = URI("#{url}/foo_ou")
48
+ open(u) do |f|
49
+ f.status[0].should == "200"
50
+ f.read.should == "foo_ou"
51
+ end
52
+ end
53
+ end
54
+
55
+ it 'should raise an ArgumentError when open is given too many args' do
56
+ lambda { open("http://192.0.2.1/tma", "r", 0666, :extra) {} }.should raise_error(ArgumentError)
57
+ end
58
+
59
+ it 'should raise an ArgumentError when open is given an invalid argument' do
60
+ lambda { open("http://127.0.0.1/", :invalid_option=>true) {} }.should raise_error(ArgumentError)
61
+ end
62
+
63
+ it 'should raise a Timeout::Error when a read times out' do
64
+ TCPServer.open("127.0.0.1", 0) do |serv|
65
+ port = serv.addr[1]
66
+ th = Thread.new {
67
+ sock = serv.accept
68
+ begin
69
+ req = sock.gets("\r\n\r\n")
70
+ req.should match %r{\AGET /foo/bar }
71
+ sock.print "HTTP/1.0 200 OK\r\n"
72
+ sock.print "Content-Length: 4\r\n\r\n"
73
+ sleep 1
74
+ sock.print "ab\r\n"
75
+ ensure
76
+ sock.close
77
+ end
78
+ }
79
+ begin
80
+ lambda { URI("http://127.0.0.1:#{port}/foo/bar").read(:read_timeout=>0.01) }.should raise_error(Timeout::Error)
81
+ ensure
82
+ Thread.kill(th)
83
+ th.join
84
+ end
85
+ end
86
+ end
87
+
88
+ it 'should read when opening in various modes' do
89
+ with_http do |srv, dr, url|
90
+ open("#{dr}/mode", "w") {|f| f << "mode" }
91
+ open("#{url}/mode", "r") do |f|
92
+ f.status[0].should == "200"
93
+ f.read.should == "mode"
94
+ end
95
+ open("#{url}/mode", "r", 0600) do |f|
96
+ f.status[0].should == "200"
97
+ f.read.should == "mode"
98
+ end
99
+ lambda { open("#{url}/mode", "a") {} }.should raise_error(ArgumentError)
100
+ open("#{url}/mode", "r:us-ascii") do |f|
101
+ f.read.encoding.should == Encoding::US_ASCII
102
+ end
103
+ open("#{url}/mode", "r:utf-8") do |f|
104
+ f.read.encoding.should == Encoding::UTF_8
105
+ end
106
+ lambda { open("#{url}/mode", "r:invalid-encoding") {} }.should raise_error(ArgumentError)
107
+ end
108
+ end
109
+
110
+ it 'should open a url when a block is not specified' do
111
+ with_http do |srv, dr, url|
112
+ open("#{dr}/without_block", "w") {|g| g << "without_block" }
113
+ begin
114
+ f = open("#{url}/without_block")
115
+ f.status[0].should == "200"
116
+ f.read.should == "without_block"
117
+ ensure
118
+ f.close
119
+ end
120
+ end
121
+ end
122
+
123
+ it 'should read the same headers for header1 and header2' do
124
+ myheader1 = 'barrrr'
125
+ myheader2 = nil
126
+ with_http do |srv, dr, url|
127
+ srv.mount_proc("/h/") {|req, res| myheader2 = req['myheader']; res.body = "foo" }
128
+ open("#{url}/h/", 'MyHeader'=>myheader1) do |f|
129
+ f.read.should == "foo"
130
+ myheader1.should == myheader2
131
+ end
132
+ end
133
+ end
134
+
135
+ it 'should not open with multiple proxy options' do
136
+ lambda { open("http://127.0.0.1/", :proxy_http_basic_authentication=>true, :proxy=>true) {} }.should raise_error(ArgumentError)
137
+ end
138
+
139
+ it 'should not open with a non-http proxy' do
140
+ lambda { open("http://127.0.0.1/", :proxy=>URI("ftp://127.0.0.1/")) {} }.should raise_error(RuntimeError)
141
+ end
142
+
143
+ it 'should open a url via a proxy' do
144
+ with_http do |srv, dr, url|
145
+ log = ''
146
+ proxy = WEBrick::HTTPProxyServer.new({
147
+ :ServerType => Thread,
148
+ :Logger => WEBrick::Log.new(NullLog),
149
+ :AccessLog => [[NullLog, ""]],
150
+ :ProxyAuthProc => lambda {|req, res|
151
+ log << req.request_line
152
+ },
153
+ :BindAddress => '127.0.0.1',
154
+ :Port => 0})
155
+ _, proxy_port, _, proxy_host = proxy.listeners[0].addr
156
+ proxy_url = "http://#{proxy_host}:#{proxy_port}/"
157
+ begin
158
+ th = proxy.start
159
+ open("#{dr}/proxy", "w") {|f| f << "proxy" }
160
+ open("#{url}/proxy", :proxy=>proxy_url) do |f|
161
+ f.status[0].should == "200"
162
+ f.read.should == "proxy"
163
+ end
164
+ log.should match /#{Regexp.quote url}/
165
+ log.clear
166
+
167
+ open("#{url}/proxy", :proxy=>URI(proxy_url)) do |f|
168
+ f.status[0].should == "200"
169
+ f.read.should == "proxy"
170
+ end
171
+ log.should match /#{Regexp.quote url}/
172
+ log.clear
173
+
174
+ open("#{url}/proxy", :proxy=>nil) do |f|
175
+ f.status[0].should == "200"
176
+ f.read.should == "proxy"
177
+ end
178
+ log.should == ""
179
+ log.clear
180
+
181
+ lambda { open("#{url}/proxy", :proxy=>:invalid) {} }.should raise_error(ArgumentError)
182
+ log.should == ""
183
+ log.clear
184
+
185
+ with_env("http_proxy"=>proxy_url) {
186
+ # should not use proxy for 127.0.0.0/8.
187
+ open("#{url}/proxy") do |f|
188
+ f.status[0].should == "200"
189
+ f.read.should == "proxy"
190
+ end
191
+ }
192
+ log.should == ""
193
+ log.clear
194
+ ensure
195
+ proxy.shutdown
196
+ end
197
+ end
198
+ end
199
+
200
+ it 'should open a url via proxy with http basic authentication' do
201
+ with_http do |srv, dr, url|
202
+ log = ''
203
+ proxy = WEBrick::HTTPProxyServer.new({
204
+ :ServerType => Thread,
205
+ :Logger => WEBrick::Log.new(NullLog),
206
+ :AccessLog => [[NullLog, ""]],
207
+ :ProxyAuthProc => lambda {|req, res|
208
+ log << req.request_line
209
+ if req["Proxy-Authorization"] != "Basic #{['user:pass'].pack('m').chomp}"
210
+ raise WEBrick::HTTPStatus::ProxyAuthenticationRequired
211
+ end
212
+ },
213
+ :BindAddress => '127.0.0.1',
214
+ :Port => 0})
215
+ _, proxy_port, _, proxy_host = proxy.listeners[0].addr
216
+ proxy_url = "http://#{proxy_host}:#{proxy_port}/"
217
+ begin
218
+ th = proxy.start
219
+ open("#{dr}/proxy", "w") {|f| f << "proxy" }
220
+ lambda { open("#{url}/proxy", :proxy=>proxy_url) {} }.should raise_error(OpenURI::HTTPError)
221
+ #TODO: somehow extract the status from the lambda
222
+ #exc.io.status[0].should == "407"
223
+ log.should match /#{Regexp.quote url}/
224
+ log.clear
225
+
226
+ open("#{url}/proxy", :proxy_http_basic_authentication=>[proxy_url, "user", "pass"]) do |f|
227
+ f.status[0].should == "200"
228
+ f.read.should == "proxy"
229
+ end
230
+ log.should match /#{Regexp.quote url}/
231
+ log.clear
232
+
233
+ lambda { open("#{url}/proxy", :proxy_http_basic_authentication=>[true, "user", "pass"]) {} }.should raise_error(ArgumentError)
234
+ log.should == ""
235
+ log.clear
236
+ ensure
237
+ proxy.shutdown
238
+ end
239
+ end
240
+ end
241
+
242
+ it 'should open urls with redirects' do
243
+ with_http do |srv, dr, url|
244
+ srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" }
245
+ srv.mount_proc("/r2/") {|req, res| res.body = "r2" }
246
+ srv.mount_proc("/to-file/") {|req, res| res.status = 301; res["location"] = "file:///foo" }
247
+ open("#{url}/r1/") do |f|
248
+ f.base_uri.to_s.should == "#{url}/r2"
249
+ f.read.should == "r2"
250
+ end
251
+ lambda { open("#{url}/r1/", :redirect=>false) {} }.should raise_error(OpenURI::HTTPRedirect)
252
+ lambda { open("#{url}/to-file/") {} }.should raise_error(RuntimeError)
253
+ end
254
+ end
255
+
256
+ it 'should raise a RuntimeError if it enters a redirect loop' do
257
+ with_http do |srv, dr, url|
258
+ srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2"; res.body = "r1" }
259
+ srv.mount_proc("/r2/") {|req, res| res.status = 301; res["location"] = "#{url}/r1"; res.body = "r2" }
260
+ lambda { open("#{url}/r1/") {} }.should raise_error(RuntimeError)
261
+ end
262
+ end
263
+
264
+ it 'should open a URI through a relative redirect' do
265
+ TCPServer.open("127.0.0.1", 0) do |serv|
266
+ port = serv.addr[1]
267
+ th = Thread.new {
268
+ sock = serv.accept
269
+ begin
270
+ req = sock.gets("\r\n\r\n")
271
+ req.should match %r{\AGET /foo/bar }
272
+ sock.print "HTTP/1.0 302 Found\r\n"
273
+ sock.print "Location: ../baz\r\n\r\n"
274
+ ensure
275
+ sock.close
276
+ end
277
+ sock = serv.accept
278
+ begin
279
+ req = sock.gets("\r\n\r\n")
280
+ req.should match %r{\AGET /baz }
281
+ sock.print "HTTP/1.0 200 OK\r\n"
282
+ sock.print "Content-Length: 4\r\n\r\n"
283
+ sock.print "ab\r\n"
284
+ ensure
285
+ sock.close
286
+ end
287
+ }
288
+ begin
289
+ content = URI("http://127.0.0.1:#{port}/foo/bar").read
290
+ content.should == "ab\r\n"
291
+ ensure
292
+ Thread.kill(th)
293
+ th.join
294
+ end
295
+ end
296
+ end
297
+
298
+ it 'should raise a OpenURI::HTTPError if an invalid redirect is encountered' do
299
+ TCPServer.open("127.0.0.1", 0) do |serv|
300
+ port = serv.addr[1]
301
+ th = Thread.new {
302
+ sock = serv.accept
303
+ begin
304
+ req = sock.gets("\r\n\r\n")
305
+ req.should match %r{\AGET /foo/bar }
306
+ sock.print "HTTP/1.0 302 Found\r\n"
307
+ sock.print "Location: ::\r\n\r\n"
308
+ ensure
309
+ sock.close
310
+ end
311
+ }
312
+ begin
313
+ lambda { URI("http://127.0.0.1:#{port}/foo/bar").read }.should raise_error(OpenURI::HTTPError)
314
+ ensure
315
+ Thread.kill(th)
316
+ th.join
317
+ end
318
+ end
319
+ end
320
+
321
+ it 'should open a url with basic authentication after being redirected' do
322
+ with_http do |srv, dr, url|
323
+ srv.mount_proc("/r1/") {|req, res| res.status = 301; res["location"] = "#{url}/r2" }
324
+ srv.mount_proc("/r2/") do |req, res|
325
+ if req["Authorization"] != "Basic #{['user:pass'].pack('m').chomp}"
326
+ raise WEBrick::HTTPStatus::Unauthorized
327
+ end
328
+ res.body = "r2"
329
+ end
330
+ lambda{ open("#{url}/r2/") {} }.should raise_error(OpenURI::HTTPError)
331
+ #TODO: somehow get the status from the lambda
332
+ #exc.io.status[0].should == "401"
333
+ open("#{url}/r2/", :http_basic_authentication=>['user', 'pass']) do |f|
334
+ f.read.should == "r2"
335
+ end
336
+ lambda { open("#{url}/r1/", :http_basic_authentication=>['user', 'pass']) {} }.should raise_error(OpenURI::HTTPError)
337
+ #TODO: somehow get the status from the lambda
338
+ #exc.io.status[0].should == "401"
339
+ end
340
+ end
341
+
342
+ it 'should raise an ArgumentError if user information is incorrectly specified in the url' do
343
+ if "1.9.0" <= RUBY_VERSION
344
+ lambda { open("http://user:pass@127.0.0.1/") {} }.should raise_error(ArgumentError)
345
+ end
346
+ end
347
+
348
+ it 'should open a url and report the progress' do
349
+ with_http do |srv, dr, url|
350
+ content = "a" * 100000
351
+ srv.mount_proc("/data/") {|req, res| res.body = content }
352
+ length = []
353
+ progress = []
354
+ open("#{url}/data/",
355
+ :content_length_proc => lambda {|n| length << n },
356
+ :progress_proc => lambda {|n| progress << n }
357
+ ) {|f|
358
+ length.length.should == 1
359
+ length[0].should == content.length
360
+ progress.length.should be > 1
361
+ progress.should == progress.sort
362
+ progress[-1].should == content.length
363
+ f.read.should == content
364
+ }
365
+ end
366
+ end
367
+
368
+ it 'should open a url and report the progress when the response is chunked' do
369
+ with_http do |srv, dr, url|
370
+ content = "a" * 100000
371
+ srv.mount_proc("/data/") {|req, res| res.body = content; res.chunked = true }
372
+ length = []
373
+ progress = []
374
+ open("#{url}/data/",
375
+ :content_length_proc => lambda {|n| length << n },
376
+ :progress_proc => lambda {|n| progress << n }
377
+ ) {|f|
378
+ length.length.should == 1
379
+ length[0].should be nil
380
+ progress.length.should be > 1
381
+ progress.should == progress.sort
382
+ progress[-1].should == content.length
383
+ f.read.should == content
384
+ }
385
+ end
386
+ end
387
+
388
+ it 'should open a url and read from it' do
389
+ with_http do |srv, dr, url|
390
+ open("#{dr}/uriread", "w") {|f| f << "uriread" }
391
+ data = URI("#{url}/uriread").read
392
+ data.status[0].should == "200"
393
+ data.should == "uriread"
394
+ end
395
+ end
396
+
397
+ it 'should work with different encodings' do
398
+ with_http do |srv, dr, url|
399
+ content_u8 = "\u3042"
400
+ content_ej = "\xa2\xa4".force_encoding("euc-jp")
401
+ srv.mount_proc("/u8/") {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset=utf-8' }
402
+ srv.mount_proc("/ej/") {|req, res| res.body = content_ej; res['content-type'] = 'TEXT/PLAIN; charset=EUC-JP' }
403
+ srv.mount_proc("/nc/") {|req, res| res.body = "aa"; res['content-type'] = 'Text/Plain' }
404
+ open("#{url}/u8/") do |f|
405
+ f.read.should == content_u8
406
+ f.content_type.should == "text/plain"
407
+ f.charset.should == "utf-8"
408
+ end
409
+ open("#{url}/ej/") do |f|
410
+ f.read.should == content_ej
411
+ f.content_type.should == "text/plain"
412
+ f.charset.should == "euc-jp"
413
+ end
414
+ open("#{url}/nc/") do |f|
415
+ f.read.should == "aa"
416
+ f.content_type.should == "text/plain"
417
+ f.charset.should == "iso-8859-1"
418
+ f.charset { "unknown" }.should == "unknown"
419
+ end
420
+ end
421
+ end
422
+
423
+ it 'should open a url with quoted attribute values' do
424
+ with_http do |srv, dr, url|
425
+ content_u8 = "\u3042"
426
+ srv.mount_proc("/qu8/") {|req, res| res.body = content_u8; res['content-type'] = 'text/plain; charset="utf\-8"' }
427
+ open("#{url}/qu8/") do |f|
428
+ f.read.should == content_u8
429
+ f.content_type.should == "text/plain"
430
+ f.charset.should == "utf-8"
431
+ end
432
+ end
433
+ end
434
+
435
+ it 'should read the last modified date from a url' do
436
+ with_http do |srv, dr, url|
437
+ srv.mount_proc("/data/") {|req, res| res.body = "foo"; res['last-modified'] = 'Fri, 07 Aug 2009 06:05:04 GMT' }
438
+ open("#{url}/data/") do |f|
439
+ f.read.should == "foo"
440
+ f.last_modified.should == Time.utc(2009,8,7,6,5,4)
441
+ end
442
+ end
443
+ end
444
+
445
+ it 'should read data with various content encoding' do
446
+ with_http do |srv, dr, url|
447
+ content = "abc" * 10000
448
+ Zlib::GzipWriter.wrap(StringIO.new(content_gz="".force_encoding("ascii-8bit"))) {|z| z.write content }
449
+ srv.mount_proc("/data/") {|req, res| res.body = content_gz; res['content-encoding'] = 'gzip' }
450
+ srv.mount_proc("/data2/") {|req, res| res.body = content_gz; res['content-encoding'] = 'gzip'; res.chunked = true }
451
+ srv.mount_proc("/noce/") {|req, res| res.body = content_gz }
452
+ open("#{url}/data/") do |f|
453
+ f.content_encoding.should == ['gzip']
454
+ f.read.force_encoding("ascii-8bit").should == content_gz
455
+ end
456
+ open("#{url}/data2/") do |f|
457
+ f.content_encoding.should == ['gzip']
458
+ f.read.force_encoding("ascii-8bit").should == content_gz
459
+ end
460
+ open("#{url}/noce/") do |f|
461
+ f.content_encoding.should == []
462
+ f.read.force_encoding("ascii-8bit").should == content_gz
463
+ end
464
+ end
465
+ end
466
+
467
+ # 192.0.2.0/24 is TEST-NET. [RFC3330]
468
+
469
+ it 'should find the proxy for a given url' do
470
+ URI("http://192.0.2.1/").find_proxy.should be nil
471
+ URI("ftp://192.0.2.1/").find_proxy.should be nil
472
+ with_env('http_proxy'=>'http://127.0.0.1:8080') {
473
+ URI("http://192.0.2.1/").find_proxy.should == URI('http://127.0.0.1:8080')
474
+ URI("ftp://192.0.2.1/").find_proxy.should be nil
475
+ }
476
+ with_env('ftp_proxy'=>'http://127.0.0.1:8080') {
477
+ URI("http://192.0.2.1/").find_proxy.should be nil
478
+ URI("ftp://192.0.2.1/").find_proxy.should == URI('http://127.0.0.1:8080')
479
+ }
480
+ with_env('REQUEST_METHOD'=>'GET') {
481
+ URI("http://192.0.2.1/").find_proxy.should be nil
482
+ }
483
+ with_env('CGI_HTTP_PROXY'=>'http://127.0.0.1:8080', 'REQUEST_METHOD'=>'GET') {
484
+ URI("http://192.0.2.1/").find_proxy.should == URI('http://127.0.0.1:8080')
485
+ }
486
+ with_env('http_proxy'=>'http://127.0.0.1:8080', 'no_proxy'=>'192.0.2.2') {
487
+ URI("http://192.0.2.1/").find_proxy.should == URI('http://127.0.0.1:8080')
488
+ URI("http://192.0.2.2/").find_proxy.should be nil
489
+ }
490
+ end
491
+
492
+ it 'should find a proxy for a given url with a case sensitive env' do
493
+ with_env('http_proxy'=>'http://127.0.0.1:8080', 'REQUEST_METHOD'=>'GET') {
494
+ URI("http://192.0.2.1/").find_proxy.should == URI('http://127.0.0.1:8080')
495
+ }
496
+ with_env('HTTP_PROXY'=>'http://127.0.0.1:8081', 'REQUEST_METHOD'=>'GET') {
497
+ URI("http://192.0.2.1/").find_proxy.should be nil
498
+ }
499
+ with_env('http_proxy'=>'http://127.0.0.1:8080', 'HTTP_PROXY'=>'http://127.0.0.1:8081', 'REQUEST_METHOD'=>'GET') {
500
+ URI("http://192.0.2.1/").find_proxy.should == URI('http://127.0.0.1:8080')
501
+ }
502
+ end unless RUBY_PLATFORM =~ /mswin|mingw/
503
+
504
+ it 'should raise exceptions on invalid ftp requests' do
505
+ lambda { URI("ftp://127.0.0.1/").read }.should raise_error(ArgumentError)
506
+ lambda { URI("ftp://127.0.0.1/a%0Db").read }.should raise_error(ArgumentError)
507
+ lambda { URI("ftp://127.0.0.1/a%0Ab").read }.should raise_error(ArgumentError)
508
+ lambda { URI("ftp://127.0.0.1/a%0Db/f").read }.should raise_error(ArgumentError)
509
+ lambda { URI("ftp://127.0.0.1/a%0Ab/f").read }.should raise_error(ArgumentError)
510
+ lambda { URI("ftp://127.0.0.1/d/f;type=x") }.should raise_error(URI::InvalidComponentError)
511
+ end
512
+
513
+ it 'should perform FTP operations' do
514
+ TCPServer.open("127.0.0.1", 0) {|serv|
515
+ _, port, _, host = serv.addr
516
+ th = Thread.new {
517
+ s = serv.accept
518
+ begin
519
+ s.print "220 Test FTP Server\r\n"
520
+ s.gets.should == "USER anonymous\r\n"; s.print "331 name ok\r\n"
521
+ s.gets.should match /\APASS .*\r\n\z/; s.print "230 logged in\r\n"
522
+ s.gets.should == "TYPE I\r\n"; s.print "200 type set to I\r\n"
523
+ s.gets.should == "CWD foo\r\n"; s.print "250 CWD successful\r\n"
524
+ s.gets.should == "PASV\r\n"
525
+ TCPServer.open("127.0.0.1", 0) {|data_serv|
526
+ _, data_serv_port, _, data_serv_host = data_serv.addr
527
+ hi = data_serv_port >> 8
528
+ lo = data_serv_port & 0xff
529
+ s.print "227 Entering Passive Mode (127,0,0,1,#{hi},#{lo}).\r\n"
530
+ s.gets.should == "RETR bar\r\n"; s.print "150 file okay\r\n"
531
+ data_sock = data_serv.accept
532
+ begin
533
+ data_sock << "content"
534
+ ensure
535
+ data_sock.close
536
+ end
537
+ s.print "226 transfer complete\r\n"
538
+ s.gets.should be nil
539
+ }
540
+ ensure
541
+ s.close if s
542
+ end
543
+ }
544
+ begin
545
+ content = URI("ftp://#{host}:#{port}/foo/bar").read
546
+ content.should == "content"
547
+ ensure
548
+ Thread.kill(th)
549
+ th.join
550
+ end
551
+ }
552
+ end
553
+
554
+ it 'should perform active FTP operations' do
555
+ TCPServer.open("127.0.0.1", 0) do |serv|
556
+ _, port, _, host = serv.addr
557
+ th = Thread.new {
558
+ s = serv.accept
559
+ begin
560
+ content = "content"
561
+ s.print "220 Test FTP Server\r\n"
562
+ s.gets.should == "USER anonymous\r\n"; s.print "331 name ok\r\n"
563
+ s.gets.should match /\APASS .*\r\n\z/; s.print "230 logged in\r\n"
564
+ s.gets.should == "TYPE I\r\n"; s.print "200 type set to I\r\n"
565
+ s.gets.should == "CWD foo\r\n"; s.print "250 CWD successful\r\n"
566
+ m = s.gets.should match /\APORT 127,0,0,1,(\d+),(\d+)\r\n\z/
567
+ active_port = m[1].to_i << 8 | m[2].to_i
568
+ TCPSocket.open("127.0.0.1", active_port) do |data_sock|
569
+ s.print "200 data connection opened\r\n"
570
+ s.gets.should == "RETR bar\r\n"; s.print "150 file okay\r\n"
571
+ begin
572
+ data_sock << content
573
+ ensure
574
+ data_sock.close
575
+ end
576
+ s.print "226 transfer complete\r\n"
577
+ s.gets.should be nil
578
+ end
579
+ ensure
580
+ s.close if s
581
+ end
582
+ }
583
+ begin
584
+ content = URI("ftp://#{host}:#{port}/foo/bar").read(:ftp_active_mode=>true)
585
+ content.should == "content"
586
+ ensure
587
+ Thread.kill(th)
588
+ th.join
589
+ end
590
+ end
591
+ end
592
+
593
+ it 'should perform FTP operations in ascii' do
594
+ TCPServer.open("127.0.0.1", 0) do |serv|
595
+ _, port, _, host = serv.addr
596
+ th = Thread.new {
597
+ s = serv.accept
598
+ begin
599
+ content = "content"
600
+ s.print "220 Test FTP Server\r\n"
601
+ s.gets.should == "USER anonymous\r\n"; s.print "331 name ok\r\n"
602
+ s.gets.should match /\APASS .*\r\n\z/; s.print "230 logged in\r\n"
603
+ s.gets.should == "TYPE I\r\n"; s.print "200 type set to I\r\n"
604
+ s.gets.should == "CWD /foo\r\n"; s.print "250 CWD successful\r\n"
605
+ s.gets.should == "TYPE A\r\n"; s.print "200 type set to A\r\n"
606
+ s.gets.should == "SIZE bar\r\n"; s.print "213 #{content.bytesize}\r\n"
607
+ s.gets.should == "PASV\r\n"
608
+ TCPServer.open("127.0.0.1", 0) {|data_serv|
609
+ _, data_serv_port, _, data_serv_host = data_serv.addr
610
+ hi = data_serv_port >> 8
611
+ lo = data_serv_port & 0xff
612
+ s.print "227 Entering Passive Mode (127,0,0,1,#{hi},#{lo}).\r\n"
613
+ s.gets.should == "RETR bar\r\n"; s.print "150 file okay\r\n"
614
+ data_sock = data_serv.accept
615
+ begin
616
+ data_sock << content
617
+ ensure
618
+ data_sock.close
619
+ end
620
+ s.print "226 transfer complete\r\n"
621
+ s.gets.should be nil
622
+ }
623
+ ensure
624
+ s.close if s
625
+ end
626
+ }
627
+ begin
628
+ length = []
629
+ progress = []
630
+ content = URI("ftp://#{host}:#{port}/%2Ffoo/b%61r;type=a").read(
631
+ :content_length_proc => lambda {|n| length << n },
632
+ :progress_proc => lambda {|n| progress << n })
633
+ content.should == "content"
634
+ length.should == [7]
635
+ progress.inject(&:+).should == 7
636
+ ensure
637
+ Thread.kill(th)
638
+ th.join
639
+ end
640
+ end
641
+ end
642
+
643
+ it 'should allow ftp via an http proxy' do
644
+ TCPServer.open("127.0.0.1", 0) do |proxy_serv|
645
+ proxy_port = proxy_serv.addr[1]
646
+ th = Thread.new {
647
+ proxy_sock = proxy_serv.accept
648
+ begin
649
+ req = proxy_sock.gets("\r\n\r\n")
650
+ req.should match %r{\AGET ftp://192.0.2.1/foo/bar }
651
+ proxy_sock.print "HTTP/1.0 200 OK\r\n"
652
+ proxy_sock.print "Content-Length: 4\r\n\r\n"
653
+ proxy_sock.print "ab\r\n"
654
+ ensure
655
+ proxy_sock.close
656
+ end
657
+ }
658
+ begin
659
+ with_env('ftp_proxy'=>"http://127.0.0.1:#{proxy_port}") {
660
+ content = URI("ftp://192.0.2.1/foo/bar").read
661
+ content.should == "ab\r\n"
662
+ }
663
+ ensure
664
+ Thread.kill(th)
665
+ th.join
666
+ end
667
+ end
668
+ end
669
+
670
+ it 'should allow ftp via an http proxy with basic authorization' do
671
+ TCPServer.open("127.0.0.1", 0) do |proxy_serv|
672
+ proxy_port = proxy_serv.addr[1]
673
+ th = Thread.new {
674
+ proxy_sock = proxy_serv.accept
675
+ begin
676
+ req = proxy_sock.gets("\r\n\r\n")
677
+ req.should match %r{\AGET ftp://192.0.2.1/foo/bar }
678
+ req.should match %r{Proxy-Authorization: Basic #{['proxy-user:proxy-password'].pack('m').chomp}\r\n}
679
+ proxy_sock.print "HTTP/1.0 200 OK\r\n"
680
+ proxy_sock.print "Content-Length: 4\r\n\r\n"
681
+ proxy_sock.print "ab\r\n"
682
+ ensure
683
+ proxy_sock.close
684
+ end
685
+ }
686
+ begin
687
+ content = URI("ftp://192.0.2.1/foo/bar").read(
688
+ :proxy_http_basic_authentication => ["http://127.0.0.1:#{proxy_port}", "proxy-user", "proxy-password"])
689
+ content.should == "ab\r\n"
690
+ ensure
691
+ Thread.kill(th)
692
+ th.join
693
+ end
694
+ end
695
+ end
696
+ end
697
+ end