diff options
Diffstat (limited to 'test/net')
| -rw-r--r-- | test/net/fixtures/Makefile | 6 | ||||
| -rw-r--r-- | test/net/fixtures/cacert.pem | 44 | ||||
| -rw-r--r-- | test/net/fixtures/server.crt | 99 | ||||
| -rw-r--r-- | test/net/fixtures/server.key | 55 | ||||
| -rw-r--r-- | test/net/ftp/test_buffered_socket.rb | 48 | ||||
| -rw-r--r-- | test/net/ftp/test_ftp.rb | 2474 | ||||
| -rw-r--r-- | test/net/ftp/test_mlsx_entry.rb | 98 | ||||
| -rw-r--r-- | test/net/http/test_http.rb | 335 | ||||
| -rw-r--r-- | test/net/http/test_http_request.rb | 41 | ||||
| -rw-r--r-- | test/net/http/test_httpheader.rb | 41 | ||||
| -rw-r--r-- | test/net/http/test_httpresponse.rb | 287 | ||||
| -rw-r--r-- | test/net/http/test_https.rb | 266 | ||||
| -rw-r--r-- | test/net/http/test_https_proxy.rb | 51 | ||||
| -rw-r--r-- | test/net/http/utils.rb | 373 | ||||
| -rw-r--r-- | test/net/imap/test_imap.rb | 785 | ||||
| -rw-r--r-- | test/net/imap/test_imap_response_parser.rb | 324 | ||||
| -rw-r--r-- | test/net/pop/test_pop.rb | 166 | ||||
| -rw-r--r-- | test/net/protocol/test_protocol.rb | 37 | ||||
| -rw-r--r-- | test/net/smtp/test_response.rb | 100 | ||||
| -rw-r--r-- | test/net/smtp/test_smtp.rb | 200 | ||||
| -rw-r--r-- | test/net/smtp/test_ssl_socket.rb | 97 |
21 files changed, 1291 insertions, 4636 deletions
diff --git a/test/net/fixtures/Makefile b/test/net/fixtures/Makefile index b2bc9c7368..88c232e3b6 100644 --- a/test/net/fixtures/Makefile +++ b/test/net/fixtures/Makefile @@ -5,11 +5,11 @@ regen_certs: make server.crt cacert.pem: server.key - openssl req -new -x509 -days 1825 -key server.key -out cacert.pem -text -subj "/C=JP/ST=Shimane/L=Matz-e city/O=Ruby Core Team/CN=Ruby Test CA/emailAddress=security@ruby-lang.org" + openssl req -new -x509 -days 3650 -key server.key -out cacert.pem -subj "/C=JP/ST=Shimane/L=Matz-e city/O=Ruby Core Team/CN=Ruby Test CA/emailAddress=security@ruby-lang.org" server.csr: - openssl req -new -key server.key -out server.csr -text -subj "/C=JP/ST=Shimane/O=Ruby Core Team/OU=Ruby Test/CN=localhost" + openssl req -new -key server.key -out server.csr -subj "/C=JP/ST=Shimane/O=Ruby Core Team/OU=Ruby Test/CN=localhost" server.crt: server.csr cacert.pem - openssl x509 -days 1825 -CA cacert.pem -CAkey server.key -set_serial 00 -in server.csr -req -text -out server.crt + openssl x509 -days 3650 -CA cacert.pem -CAkey server.key -set_serial 00 -in server.csr -req -out server.crt rm server.csr diff --git a/test/net/fixtures/cacert.pem b/test/net/fixtures/cacert.pem index f623bd62ed..24c83f1c65 100644 --- a/test/net/fixtures/cacert.pem +++ b/test/net/fixtures/cacert.pem @@ -1,24 +1,24 @@ -----BEGIN CERTIFICATE----- -MIID7TCCAtWgAwIBAgIJAIltvxrFAuSnMA0GCSqGSIb3DQEBCwUAMIGMMQswCQYD -VQQGEwJKUDEQMA4GA1UECAwHU2hpbWFuZTEUMBIGA1UEBwwLTWF0ei1lIGNpdHkx -FzAVBgNVBAoMDlJ1YnkgQ29yZSBUZWFtMRUwEwYDVQQDDAxSdWJ5IFRlc3QgQ0Ex -JTAjBgkqhkiG9w0BCQEWFnNlY3VyaXR5QHJ1YnktbGFuZy5vcmcwHhcNMTkwMTAy -MDI1ODI4WhcNMjQwMTAxMDI1ODI4WjCBjDELMAkGA1UEBhMCSlAxEDAOBgNVBAgM -B1NoaW1hbmUxFDASBgNVBAcMC01hdHotZSBjaXR5MRcwFQYDVQQKDA5SdWJ5IENv -cmUgVGVhbTEVMBMGA1UEAwwMUnVieSBUZXN0IENBMSUwIwYJKoZIhvcNAQkBFhZz -ZWN1cml0eUBydWJ5LWxhbmcub3JnMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB -CgKCAQEAznlbjRVhz1NlutHVrhcGnK8W0qug2ujKXv1njSC4U6nJF6py7I9EeehV -SaKePyv+I9z3K1LnfUHOtUbdwdKC77yN66A6q2aqzu5q09/NSykcZGOIF0GuItYI -3nvW3IqBddff2ffsyR+9pBjfb5AIPP08WowF9q4s1eGULwZc4w2B8PFhtxYANd7d -BvGLXFlcufv9tDtzyRi4t7eqxCRJkZQIZNZ6DHHIJrNxejOILfHLarI12yk8VK6L -2LG4WgGqyeePiRyd1o1MbuiAFYqAwpXNUbRKg5NaZGwBHZk8UZ+uFKt1QMBURO5R -WFy1c349jbWszTqFyL4Lnbg9HhAowQIDAQABo1AwTjAdBgNVHQ4EFgQU9tEiKdU9 -I9derQyc5nWPnc34nVMwHwYDVR0jBBgwFoAU9tEiKdU9I9derQyc5nWPnc34nVMw -DAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAxj7F/u3C3fgq24N7hGRA -of7ClFQxGmo/IGT0AISzW3HiVYiFaikKhbO1NwD9aBpD8Zwe62sCqMh8jGV/b0+q -aOORnWYNy2R6r9FkASAglmdF6xn3bhgGD5ls4pCvcG9FynGnGc24g6MrjFNrBYUS -2iIZsg36i0IJswo/Dy6HLphCms2BMCD3DeWtfjePUiTmQHJo6HsQIKP/u4N4Fvee -uMBInei2M4VU74fLXbmKl1F9AEX7JDP3BKSZG19Ch5pnUo4uXM1uNTGsi07P4Y0s -K44+SKBC0bYEFbDK0eQWMrX3kIhkPxyIWhxdq9/NqPYjShuSEAhA6CSpmRg0pqc+ -mA== +MIID+zCCAuOgAwIBAgIUGMvHl3EhtKPKcgc3NQSAYfFuC+8wDQYJKoZIhvcNAQEL +BQAwgYwxCzAJBgNVBAYTAkpQMRAwDgYDVQQIDAdTaGltYW5lMRQwEgYDVQQHDAtN +YXR6LWUgY2l0eTEXMBUGA1UECgwOUnVieSBDb3JlIFRlYW0xFTATBgNVBAMMDFJ1 +YnkgVGVzdCBDQTElMCMGCSqGSIb3DQEJARYWc2VjdXJpdHlAcnVieS1sYW5nLm9y +ZzAeFw0yNDAxMDExMTQ3MjNaFw0zMzEyMjkxMTQ3MjNaMIGMMQswCQYDVQQGEwJK +UDEQMA4GA1UECAwHU2hpbWFuZTEUMBIGA1UEBwwLTWF0ei1lIGNpdHkxFzAVBgNV +BAoMDlJ1YnkgQ29yZSBUZWFtMRUwEwYDVQQDDAxSdWJ5IFRlc3QgQ0ExJTAjBgkq +hkiG9w0BCQEWFnNlY3VyaXR5QHJ1YnktbGFuZy5vcmcwggEiMA0GCSqGSIb3DQEB +AQUAA4IBDwAwggEKAoIBAQCw+egZQ6eumJKq3hfKfED4dE/tL4FI5sjqont9ABVI ++1GSqyi1bFBgsRjM0THllIdMbKmJtWwnKW8J+5OgNN8y6Xxv8JmM/Y5vQt2lis0f +qXmG8UTz0VTWdlAXXmhUs6lSADvAaIe4RVrCsZ97L3ZQTryY7JRVcbB4khUN3Gp0 +yg+801SXzoFTTa+UGIRLE66jH51aa5VXu99hnv1OiH8tQrjdi8mH6uG/icq4XuIe +NWMF32wHqIOOPvQcWV3M5D2vxJEj702Ku6k9OQXkAo17qRSEonWW4HtLbtmS8He1 +JNPc/n3dVUm+fM6NoDXPoLP7j55G9zKyqGtGAWXAj1MTAgMBAAGjUzBRMB0GA1Ud +DgQWBBSJGVleDvFp9cu9R+E0/OKYzGkwkTAfBgNVHSMEGDAWgBSJGVleDvFp9cu9 +R+E0/OKYzGkwkTAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQBl +8GLB8skAWlkSw/FwbUmEV3zyqu+p7PNP5YIYoZs0D74e7yVulGQ6PKMZH5hrZmHo +orFSQU+VUUirG8nDGj7Rzce8WeWBxsaDGC8CE2dq6nC6LuUwtbdMnBrH0LRWAz48 +jGFF3jHtVz8VsGfoZTZCjukWqNXvU6hETT9GsfU+PZqbqcTVRPH52+XgYayKdIbD +r97RM4X3+aXBHcUW0b76eyyi65RR/Xtvn8ioZt2AdX7T2tZzJyXJN3Hupp77s6Ui +AZR35SToHCZeTZD12YBvLBdaTPLZN7O/Q/aAO9ZiJaZ7SbFOjz813B2hxXab4Fob +2uJX6eMWTVxYK5D4M9lm -----END CERTIFICATE----- diff --git a/test/net/fixtures/server.crt b/test/net/fixtures/server.crt index 5ca78a6d14..5d2923795d 100644 --- a/test/net/fixtures/server.crt +++ b/test/net/fixtures/server.crt @@ -1,82 +1,21 @@ -Certificate: - Data: - Version: 3 (0x2) - Serial Number: 2 (0x2) - Signature Algorithm: sha256WithRSAEncryption - Issuer: C=JP, ST=Shimane, L=Matz-e city, O=Ruby Core Team, CN=Ruby Test CA/emailAddress=security@ruby-lang.org - Validity - Not Before: Jan 2 03:27:13 2019 GMT - Not After : Jan 1 03:27:13 2024 GMT - Subject: C=JP, ST=Shimane, O=Ruby Core Team, OU=Ruby Test, CN=localhost - Subject Public Key Info: - Public Key Algorithm: rsaEncryption - Public-Key: (2048 bit) - Modulus: - 00:e8:da:9c:01:2e:2b:10:ec:49:cd:5e:07:13:07: - 9c:70:9e:c6:74:bc:13:c2:e1:6f:c6:82:fd:e3:48: - e0:2c:a5:68:c7:9e:42:de:60:54:65:e6:6a:14:57: - 7a:30:d0:cc:b5:b6:d9:c3:d2:df:c9:25:97:54:67: - cf:f6:be:5e:cb:8b:ee:03:c5:e1:e2:f9:e7:f7:d1: - 0c:47:f0:b8:da:33:5a:ad:41:ad:e7:b5:a2:7b:b7: - bf:30:da:60:f8:e3:54:a2:bc:3a:fd:1b:74:d9:dc: - 74:42:e9:29:be:df:ac:b4:4f:eb:32:f4:06:f1:e1: - 8c:4b:a8:8b:fb:29:e7:b1:bf:1d:01:ee:73:0f:f9: - 40:dc:d5:15:79:d9:c6:73:d0:c0:dd:cb:e4:da:19: - 47:80:c6:14:04:72:fd:9a:7c:8f:11:82:76:49:04: - 79:cc:f2:5c:31:22:95:13:3e:5d:40:a6:4d:e0:a3: - 02:26:7d:52:3b:bb:ed:65:a1:0f:ed:6b:b0:3c:d4: - de:61:15:5e:d3:dd:68:09:9f:4a:57:a5:c2:a9:6d: - 86:92:c5:f4:a4:d4:b7:13:3b:52:63:24:05:e2:cc: - e3:8a:3c:d4:35:34:2b:10:bb:58:72:e7:e1:8d:1d: - 74:8c:61:16:20:3d:d0:1c:4e:8f:6e:fd:fe:64:10: - 4f:41 - Exponent: 65537 (0x10001) - X509v3 extensions: - X509v3 Basic Constraints: - CA:FALSE - Netscape Comment: - OpenSSL Generated Certificate - X509v3 Subject Key Identifier: - ED:28:C2:7E:AB:4B:C8:E8:FE:55:6D:66:95:31:1C:2D:60:F9:02:36 - X509v3 Authority Key Identifier: - keyid:F6:D1:22:29:D5:3D:23:D7:5E:AD:0C:9C:E6:75:8F:9D:CD:F8:9D:53 - - Signature Algorithm: sha256WithRSAEncryption - 1d:b8:c5:8b:72:41:20:65:ad:27:6f:15:63:06:26:12:8d:9c: - ad:ca:f4:db:97:b4:90:cb:ff:35:94:bb:2a:a7:a1:ab:1e:35: - 2d:a5:3f:c9:24:b0:1a:58:89:75:3e:81:0a:2c:4f:98:f9:51: - fb:c0:a3:09:d0:0a:9b:e7:a2:b7:c3:60:40:c8:f4:6d:b2:6a: - 56:12:17:4c:00:24:31:df:9c:60:ae:b1:68:54:a9:e6:b5:4a: - 04:e6:92:05:86:d9:5a:dc:96:30:a5:58:de:14:99:0f:e5:15: - 89:3e:9b:eb:80:e3:bd:83:c3:ea:33:35:4b:3e:2f:d3:0d:64: - 93:67:7f:8d:f5:3f:0c:27:bc:37:5a:cc:d6:47:16:af:5a:62: - d2:da:51:f8:74:06:6b:24:ad:28:68:08:98:37:7d:ed:0e:ab: - 1e:82:61:05:d0:ba:75:a0:ab:21:b0:9a:fd:2b:54:86:1d:0d: - 1f:c2:d4:77:1f:72:26:5e:ad:8a:9f:09:36:6d:44:be:74:c2: - 5a:3e:ff:5c:9d:75:d6:38:7b:c5:39:f9:44:6e:a1:d1:8e:ff: - 63:db:c4:bb:c6:91:92:ca:5c:60:9b:1d:eb:0a:de:08:ee:bf: - da:76:03:65:62:29:8b:f8:7f:c7:86:73:1e:f6:1f:2d:89:69: - fd:be:bd:6e -----BEGIN CERTIFICATE----- -MIID4zCCAsugAwIBAgIBAjANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCSlAx -EDAOBgNVBAgMB1NoaW1hbmUxFDASBgNVBAcMC01hdHotZSBjaXR5MRcwFQYDVQQK -DA5SdWJ5IENvcmUgVGVhbTEVMBMGA1UEAwwMUnVieSBUZXN0IENBMSUwIwYJKoZI -hvcNAQkBFhZzZWN1cml0eUBydWJ5LWxhbmcub3JnMB4XDTE5MDEwMjAzMjcxM1oX -DTI0MDEwMTAzMjcxM1owYDELMAkGA1UEBhMCSlAxEDAOBgNVBAgMB1NoaW1hbmUx -FzAVBgNVBAoMDlJ1YnkgQ29yZSBUZWFtMRIwEAYDVQQLDAlSdWJ5IFRlc3QxEjAQ -BgNVBAMMCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB -AOjanAEuKxDsSc1eBxMHnHCexnS8E8Lhb8aC/eNI4CylaMeeQt5gVGXmahRXejDQ -zLW22cPS38kll1Rnz/a+XsuL7gPF4eL55/fRDEfwuNozWq1Bree1onu3vzDaYPjj -VKK8Ov0bdNncdELpKb7frLRP6zL0BvHhjEuoi/sp57G/HQHucw/5QNzVFXnZxnPQ -wN3L5NoZR4DGFARy/Zp8jxGCdkkEeczyXDEilRM+XUCmTeCjAiZ9Uju77WWhD+1r -sDzU3mEVXtPdaAmfSlelwqlthpLF9KTUtxM7UmMkBeLM44o81DU0KxC7WHLn4Y0d -dIxhFiA90BxOj279/mQQT0ECAwEAAaN7MHkwCQYDVR0TBAIwADAsBglghkgBhvhC -AQ0EHxYdT3BlblNTTCBHZW5lcmF0ZWQgQ2VydGlmaWNhdGUwHQYDVR0OBBYEFO0o -wn6rS8jo/lVtZpUxHC1g+QI2MB8GA1UdIwQYMBaAFPbRIinVPSPXXq0MnOZ1j53N -+J1TMA0GCSqGSIb3DQEBCwUAA4IBAQAduMWLckEgZa0nbxVjBiYSjZytyvTbl7SQ -y/81lLsqp6GrHjUtpT/JJLAaWIl1PoEKLE+Y+VH7wKMJ0Aqb56K3w2BAyPRtsmpW -EhdMACQx35xgrrFoVKnmtUoE5pIFhtla3JYwpVjeFJkP5RWJPpvrgOO9g8PqMzVL -Pi/TDWSTZ3+N9T8MJ7w3WszWRxavWmLS2lH4dAZrJK0oaAiYN33tDqsegmEF0Lp1 -oKshsJr9K1SGHQ0fwtR3H3ImXq2Knwk2bUS+dMJaPv9cnXXWOHvFOflEbqHRjv9j -28S7xpGSylxgmx3rCt4I7r/adgNlYimL+H/HhnMe9h8tiWn9vr1u +MIIDYTCCAkkCAQAwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAkpQMRAwDgYD +VQQIDAdTaGltYW5lMRQwEgYDVQQHDAtNYXR6LWUgY2l0eTEXMBUGA1UECgwOUnVi +eSBDb3JlIFRlYW0xFTATBgNVBAMMDFJ1YnkgVGVzdCBDQTElMCMGCSqGSIb3DQEJ +ARYWc2VjdXJpdHlAcnVieS1sYW5nLm9yZzAeFw0yNDAxMDExMTQ3MjNaFw0zMzEy +MjkxMTQ3MjNaMGAxCzAJBgNVBAYTAkpQMRAwDgYDVQQIDAdTaGltYW5lMRcwFQYD +VQQKDA5SdWJ5IENvcmUgVGVhbTESMBAGA1UECwwJUnVieSBUZXN0MRIwEAYDVQQD +DAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw+egZ +Q6eumJKq3hfKfED4dE/tL4FI5sjqont9ABVI+1GSqyi1bFBgsRjM0THllIdMbKmJ +tWwnKW8J+5OgNN8y6Xxv8JmM/Y5vQt2lis0fqXmG8UTz0VTWdlAXXmhUs6lSADvA +aIe4RVrCsZ97L3ZQTryY7JRVcbB4khUN3Gp0yg+801SXzoFTTa+UGIRLE66jH51a +a5VXu99hnv1OiH8tQrjdi8mH6uG/icq4XuIeNWMF32wHqIOOPvQcWV3M5D2vxJEj +702Ku6k9OQXkAo17qRSEonWW4HtLbtmS8He1JNPc/n3dVUm+fM6NoDXPoLP7j55G +9zKyqGtGAWXAj1MTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACtGNdj5TEtnJBYp +M+LhBeU3oNteldfycEm993gJp6ghWZFg23oX8fVmyEeJr/3Ca9bAgDqg0t9a0npN +oWKEY6wVKqcHgu3gSvThF5c9KhGbeDDmlTSVVNQmXWX0K2d4lS2cwZHH8mCm2mrY +PDqlEkSc7k4qSiqigdS8i80Yk+lDXWsm8CjsiC93qaRM7DnS0WPQR0c16S95oM6G +VklFKUSDAuFjw9aVWA/nahOucjn0w5fVW6lyIlkBslC1ChlaDgJmvhz+Ol3iMsE0 +kAmFNu2KKPVrpMWaBID49QwQTDyhetNLaVVFM88iUdA9JDoVMEuP1mm39JqyzHTu +uBrdP4Q= -----END CERTIFICATE----- diff --git a/test/net/fixtures/server.key b/test/net/fixtures/server.key index 7f2380e71e..6a83d5bcf4 100644 --- a/test/net/fixtures/server.key +++ b/test/net/fixtures/server.key @@ -1,28 +1,27 @@ ------BEGIN PRIVATE KEY----- -MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDo2pwBLisQ7EnN -XgcTB5xwnsZ0vBPC4W/Ggv3jSOAspWjHnkLeYFRl5moUV3ow0My1ttnD0t/JJZdU -Z8/2vl7Li+4DxeHi+ef30QxH8LjaM1qtQa3ntaJ7t78w2mD441SivDr9G3TZ3HRC -6Sm+36y0T+sy9Abx4YxLqIv7Keexvx0B7nMP+UDc1RV52cZz0MDdy+TaGUeAxhQE -cv2afI8RgnZJBHnM8lwxIpUTPl1Apk3gowImfVI7u+1loQ/ta7A81N5hFV7T3WgJ -n0pXpcKpbYaSxfSk1LcTO1JjJAXizOOKPNQ1NCsQu1hy5+GNHXSMYRYgPdAcTo9u -/f5kEE9BAgMBAAECggEBAOHkwhc7DLh8IhTDNSW26oMu5OP2WU1jmiYAigDmf+OQ -DBgrZj+JQBci8qINQxL8XLukSZn5hvQCLc7Kbyu1/wyEEUFDxSGGwwzclodr9kho -LX2LDASPZrOSzD2+fPi2wTKmXKuS6Uc44OjQfZkYMNkz9r4Vkm8xGgOD3VipjIYX -QXlhhdqkXZcNABsihCV52GKkDFSVm8jv95YJc5xhoYCy/3a4/qPdF0aT2R7oYUej -hKrxVDskyooe8Zg/JTydZNV5GQEDmW01/K3r6XGT26oPi1AqMU1gtv/jkW56CRQQ -1got8smnqM+AV7Slf9R6DauIPdQJ2S8wsr/o8ISBsOECgYEA9YrqEP2gAYSGFXRt -liw0WI2Ant8BqXS6yvq1jLo/qWhLw/ph4Di73OQ2mpycVTpgfGr2wFPQR1XJ+0Fd -U+Ir/C3Q7FK4VIGHK7B0zNvZr5tEjlFfeRezo2JMVw5YWeSagIFcSwK+KqCTH9qc -pw/Eb8nB/4XNcpTZu7Fg0Wc+ooUCgYEA8sVaicn1Wxkpb45a4qfrA6wOr5xdJ4cC -A5qs7vjX2OdPIQOmoQhdI7bCWFXZzF33wA4YCws6j5wRaySLIJqdms8Gl9QnODy1 -ZlA5gwKToBC/jqPmWAXSKb8EH7cHilaxU9OKnQ7CfwlGLHqjMtjrhR7KHlt3CVRs -oRmvsjZVXI0CgYAmPedslAO6mMhFSSfULrhMXmV82OCqYrrA6EEkVNGbcdnzAOkD -gfKIWabDd8bFY10po4Mguy0CHzNhBXIioWQWV5BlbhC1YKMLw+S9DzSdLAKGY9gJ -xQ4+UQ3wtRQ/k+IYR413RUsW2oFvgZ3KSyNeAb9MK6uuv84VdG/OzVSs/QKBgQDn -kap//l2EbObiWyaERunckdVcW0lcN+KK75J/TGwPoOwQsLvTpPe65kxRGGrtDsEQ -uCDk/+v3KkZPLgdrrTAih9FhJ+PVN8tMcb+6IM4SA4fFFr/UPJEwct0LJ3oQ0grJ -y+HPWFHb/Uurh7t99/4H98uR02sjQh1wOeEmm78mzQKBgQDm+LzGH0se6CXQ6cdZ -g1JRZeXkDEsrW3hfAsW62xJQmXcWxBoblP9OamMY+A06rM5og3JbDk5Zm6JsOaA8 -wS2gw4ilp46jors4eQey8ux7kB9LzdBoDBBElnsbjLO8oBNZlVcYXg+6BOl/CUi7 -2whRF0FEjKA8ehrNhAq+VFfFNw== ------END PRIVATE KEY----- +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAsPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqso +tWxQYLEYzNEx5ZSHTGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE +89FU1nZQF15oVLOpUgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNU +l86BU02vlBiESxOuox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9s +B6iDjj70HFldzOQ9r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P59 +3VVJvnzOjaA1z6Cz+4+eRvcysqhrRgFlwI9TEwIDAQABAoIBAEEYiyDP29vCzx/+ +dS3LqnI5BjUuJhXUnc6AWX/PCgVAO+8A+gZRgvct7PtZb0sM6P9ZcLrweomlGezI +FrL0/6xQaa8bBr/ve/a8155OgcjFo6fZEw3Dz7ra5fbSiPmu4/b/kvrg+Br1l77J +aun6uUAs1f5B9wW+vbR7tzbT/mxaUeDiBzKpe15GwcvbJtdIVMa2YErtRjc1/5B2 +BGVXyvlJv0SIlcIEMsHgnAFOp1ZgQ08aDzvilLq8XVMOahAhP1O2A3X8hKdXPyrx +IVWE9bS9ptTo+eF6eNl+d7htpKGEZHUxinoQpWEBTv+iOoHsVunkEJ3vjLP3lyI/ +fY0NQ1ECgYEA3RBXAjgvIys2gfU3keImF8e/TprLge1I2vbWmV2j6rZCg5r/AS0u +pii5CvJ5/T5vfJPNgPBy8B/yRDs+6PJO1GmnlhOkG9JAIPkv0RBZvR0PMBtbp6nT +Y3yo1lwamBVBfY6rc0sLTzosZh2aGoLzrHNMQFMGaauORzBFpY5lU50CgYEAzPHl +u5DI6Xgep1vr8QvCUuEesCOgJg8Yh1UqVoY/SmQh6MYAv1I9bLGwrb3WW/7kqIoD +fj0aQV5buVZI2loMomtU9KY5SFIsPV+JuUpy7/+VE01ZQM5FdY8wiYCQiVZYju9X +Wz5LxMNoz+gT7pwlLCsC4N+R8aoBk404aF1gum8CgYAJ7VTq7Zj4TFV7Soa/T1eE +k9y8a+kdoYk3BASpCHJ29M5R2KEA7YV9wrBklHTz8VzSTFTbKHEQ5W5csAhoL5Fo +qoHzFFi3Qx7MHESQb9qHyolHEMNx6QdsHUn7rlEnaTTyrXh3ifQtD6C0yTmFXUIS +CW9wKApOrnyKJ9nI0HcuZQKBgQCMtoV6e9VGX4AEfpuHvAAnMYQFgeBiYTkBKltQ +XwozhH63uMMomUmtSG87Sz1TmrXadjAhy8gsG6I0pWaN7QgBuFnzQ/HOkwTm+qKw +AsrZt4zeXNwsH7QXHEJCFnCmqw9QzEoZTrNtHJHpNboBuVnYcoueZEJrP8OnUG3r +UjmopwKBgAqB2KYYMUqAOvYcBnEfLDmyZv9BTVNHbR2lKkMYqv5LlvDaBxVfilE0 +2riO4p6BaAdvzXjKeRrGNEKoHNBpOSfYCOM16NjL8hIZB1CaV3WbT5oY+jp7Mzd5 +7d56RZOE+ERK2uz/7JX9VSsM/LbH9pJibd4e8mikDS9ntciqOH/3 +-----END RSA PRIVATE KEY----- diff --git a/test/net/ftp/test_buffered_socket.rb b/test/net/ftp/test_buffered_socket.rb deleted file mode 100644 index 875c53f4e0..0000000000 --- a/test/net/ftp/test_buffered_socket.rb +++ /dev/null @@ -1,48 +0,0 @@ -# frozen_string_literal: true - -require "net/ftp" -require "test/unit" -require "ostruct" -require "stringio" - -class BufferedSocketTest < Test::Unit::TestCase - def test_gets_empty - sock = create_buffered_socket("") - assert_equal(nil, sock.gets) - end - - def test_gets_one_line - sock = create_buffered_socket("foo\n") - assert_equal("foo\n", sock.gets) - end - - def test_gets_one_line_without_term - sock = create_buffered_socket("foo") - assert_equal("foo", sock.gets) - end - - def test_gets_two_lines - sock = create_buffered_socket("foo\nbar\n") - assert_equal("foo\n", sock.gets) - assert_equal("bar\n", sock.gets) - end - - def test_gets_two_lines_without_term - sock = create_buffered_socket("foo\nbar") - assert_equal("foo\n", sock.gets) - assert_equal("bar", sock.gets) - end - - def test_read_nil - sock = create_buffered_socket("foo\nbar") - assert_equal("foo\nbar", sock.read) - assert_equal("", sock.read) - end - - private - - def create_buffered_socket(s) - io = StringIO.new(s) - return Net::FTP::BufferedSocket.new(io) - end -end diff --git a/test/net/ftp/test_ftp.rb b/test/net/ftp/test_ftp.rb deleted file mode 100644 index 39f1220dbb..0000000000 --- a/test/net/ftp/test_ftp.rb +++ /dev/null @@ -1,2474 +0,0 @@ -# frozen_string_literal: true - -require "net/ftp" -require "test/unit" -require "ostruct" -require "stringio" -require "tempfile" -require "tmpdir" - -class FTPTest < Test::Unit::TestCase - SERVER_NAME = "localhost" - SERVER_ADDR = - begin - Addrinfo.getaddrinfo(SERVER_NAME, 0, nil, :STREAM)[0].ip_address - rescue SocketError - "127.0.0.1" - end - CA_FILE = File.expand_path("../fixtures/cacert.pem", __dir__) - SERVER_KEY = File.expand_path("../fixtures/server.key", __dir__) - SERVER_CERT = File.expand_path("../fixtures/server.crt", __dir__) - - def setup - @thread = nil - @default_passive = Net::FTP.default_passive - Net::FTP.default_passive = false - end - - def teardown - Net::FTP.default_passive = @default_passive - if @thread - @thread.join - end - end - - def test_not_connected - ftp = Net::FTP.new - assert_raise(Net::FTPConnectionError) do - ftp.quit - end - end - - def test_closed_when_not_connected - ftp = Net::FTP.new - assert_equal(true, ftp.closed?) - assert_nothing_raised(Net::FTPConnectionError) do - ftp.close - end - end - - def test_connect_fail - server = create_ftp_server { |sock| - sock.print("421 Service not available, closing control connection.\r\n") - } - begin - ftp = Net::FTP.new - assert_raise(Net::FTPTempError){ ftp.connect(SERVER_ADDR, server.port) } - ensure - ftp.close if ftp - server.close - end - end - - def test_parse227 - ftp = Net::FTP.new - host, port = ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1,12,34)") - assert_equal("192.168.0.1", host) - assert_equal(3106, port) - assert_raise(Net::FTPReplyError) do - ftp.send(:parse227, "500 Syntax error") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse227, "227 Entering Passive Mode") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1,12,34,56)") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse227, "227 Entering Passive Mode (192,168,0,1)") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse227, "227 ) foo bar (") - end - end - - def test_parse228 - ftp = Net::FTP.new - host, port = ftp.send(:parse228, "228 Entering Long Passive Mode (4,4,192,168,0,1,2,12,34)") - assert_equal("192.168.0.1", host) - assert_equal(3106, port) - host, port = ftp.send(:parse228, "228 Entering Long Passive Mode (6,16,16,128,0,0,0,0,0,0,0,8,8,0,32,12,65,122,2,12,34)") - assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host) - assert_equal(3106, port) - assert_raise(Net::FTPReplyError) do - ftp.send(:parse228, "500 Syntax error") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse228, "228 Entering Passive Mode") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse228, "228 Entering Long Passive Mode (6,4,192,168,0,1,2,12,34)") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse228, "228 Entering Long Passive Mode (4,4,192,168,0,1,3,12,34,56)") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse228, "228 Entering Long Passive Mode (4,16,16,128,0,0,0,0,0,0,0,8,8,0,32,12,65,122,2,12,34)") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse228, "228 Entering Long Passive Mode (6,16,16,128,0,0,0,0,0,0,0,8,8,0,32,12,65,122,3,12,34,56)") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse228, "228 Entering Long Passive Mode (6,16,16,128,0,0,0,0,0,0,0,8,8,0,32,12,65,122,2,12,34,56)") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse227, "227 ) foo bar (") - end - end - - def test_parse229 - ftp = Net::FTP.new - sock = OpenStruct.new - sock.remote_address = OpenStruct.new - sock.remote_address.ip_address = "1080:0000:0000:0000:0008:0800:200c:417a" - ftp.instance_variable_set(:@bare_sock, sock) - host, port = ftp.send(:parse229, "229 Entering Passive Mode (|||3106|)") - assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host) - assert_equal(3106, port) - host, port = ftp.send(:parse229, "229 Entering Passive Mode (!!!3106!)") - assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host) - assert_equal(3106, port) - host, port = ftp.send(:parse229, "229 Entering Passive Mode (~~~3106~)") - assert_equal("1080:0000:0000:0000:0008:0800:200c:417a", host) - assert_equal(3106, port) - assert_raise(Net::FTPReplyError) do - ftp.send(:parse229, "500 Syntax error") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse229, "229 Entering Passive Mode") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse229, "229 Entering Passive Mode (|!!3106!)") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse229, "229 Entering Passive Mode ( 3106 )") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse229, "229 Entering Passive Mode (\x7f\x7f\x7f3106\x7f)") - end - assert_raise(Net::FTPProtoError) do - ftp.send(:parse229, "229 ) foo bar (") - end - end - - def test_parse_pasv_port - ftp = Net::FTP.new - assert_equal(12, ftp.send(:parse_pasv_port, "12")) - assert_equal(3106, ftp.send(:parse_pasv_port, "12,34")) - assert_equal(795192, ftp.send(:parse_pasv_port, "12,34,56")) - assert_equal(203569230, ftp.send(:parse_pasv_port, "12,34,56,78")) - end - - def test_login - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_login_fail1 - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("502 Command not implemented.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - assert_raise(Net::FTPPermError){ ftp.login } - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_login_fail2 - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("530 Not logged in.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - assert_raise(Net::FTPPermError){ ftp.login } - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_implicit_login - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("332 Need account for login.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new(SERVER_ADDR, - port: server.port, - username: "foo", - password: "bar", - account: "baz") - assert_equal("USER foo\r\n", commands.shift) - assert_equal("PASS bar\r\n", commands.shift) - assert_equal("ACCT baz\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_s_open - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - Net::FTP.open(SERVER_ADDR, port: server.port, username: "anonymous") do - end - assert_equal("USER anonymous\r\n", commands.shift) - assert_equal("PASS anonymous@\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - server.close - end - end - - def test_s_new_timeout_options - ftp = Net::FTP.new - assert_equal(nil, ftp.open_timeout) - assert_equal(60, ftp.read_timeout) - - ftp = Net::FTP.new(nil, open_timeout: 123, read_timeout: 234) - assert_equal(123, ftp.open_timeout) - assert_equal(234, ftp.read_timeout) - end - - # TODO: How can we test open_timeout? sleep before accept cannot delay - # connections. - def _test_open_timeout_exceeded - commands = [] - server = create_ftp_server(0.2) { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.open_timeout = 0.1 - ftp.connect(SERVER_ADDR, server.port) - assert_raise(Net::OpenTimeout) do - ftp.login - end - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_read_timeout_exceeded - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sleep(0.1) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sleep(2.0) # Net::ReadTimeout - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sleep(0.1) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.read_timeout = 0.4 - ftp.connect(SERVER_ADDR, server.port) - assert_raise(Net::ReadTimeout) do - ftp.login - end - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_read_timeout_not_exceeded - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sleep(0.1) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sleep(0.1) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sleep(0.1) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.read_timeout = 1.0 - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close - assert_equal(1.0, ftp.read_timeout) - end - ensure - server.close - end - end - - def test_list_read_timeout_exceeded - commands = [] - list_lines = [ - "-rw-r--r-- 1 0 0 0 Mar 30 11:22 foo.txt", - "-rw-r--r-- 1 0 0 0 Mar 30 11:22 bar.txt", - "-rw-r--r-- 1 0 0 0 Mar 30 11:22 baz.txt" - ] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Here comes the directory listing.\r\n") - begin - conn = TCPSocket.new(host, port) - list_lines.each_with_index do |l, i| - if i == 1 - sleep(0.5) - else - sleep(0.1) - end - conn.print(l, "\r\n") - end - rescue Errno::EPIPE - ensure - assert_nil($!) - conn.close - end - sock.print("226 Directory send OK.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.read_timeout = 0.2 - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_raise(Net::ReadTimeout) do - ftp.list - end - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("LIST\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_list_read_timeout_not_exceeded - commands = [] - list_lines = [ - "-rw-r--r-- 1 0 0 0 Mar 30 11:22 foo.txt", - "-rw-r--r-- 1 0 0 0 Mar 30 11:22 bar.txt", - "-rw-r--r-- 1 0 0 0 Mar 30 11:22 baz.txt" - ] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Here comes the directory listing.\r\n") - conn = TCPSocket.new(host, port) - list_lines.each do |l| - sleep(0.1) - conn.print(l, "\r\n") - end - conn.close - sock.print("226 Directory send OK.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.read_timeout = 1.0 - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(list_lines, ftp.list) - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("LIST\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_list_fail - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("553 Requested action not taken.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - [host, port] - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_raise(Net::FTPPermError){ ftp.list } - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("LIST\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_open_data_port_fail_no_leak - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - sock.print("421 Service not available, closing control connection.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_raise(Net::FTPTempError){ ftp.list } - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_retrbinary_read_timeout_exceeded - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - sleep(0.1) - conn.print(binary_data[0,1024]) - sleep(1.0) - conn.print(binary_data[1024, 1024]) rescue nil # may raise EPIPE or something - conn.close - sock.print("226 Transfer complete.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.read_timeout = 0.5 - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = String.new - assert_raise(Net::ReadTimeout) do - ftp.retrbinary("RETR foo", 1024) do |s| - buf << s - end - end - assert_equal(1024, buf.bytesize) - assert_equal(binary_data[0, 1024], buf) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close unless ftp.closed? - end - ensure - server.close - end - end - - def test_retrbinary_read_timeout_not_exceeded - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - binary_data.scan(/.{1,1024}/nm) do |s| - sleep(0.2) - conn.print(s) - end - conn.shutdown(Socket::SHUT_WR) - conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.read_timeout = 1.0 - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = String.new - ftp.retrbinary("RETR foo", 1024) do |s| - buf << s - end - assert_equal(binary_data.bytesize, buf.bytesize) - assert_equal(binary_data, buf) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_retrbinary_fail - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("550 Requested action not taken.\r\n") - [host, port] - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_raise(Net::FTPPermError){ ftp.retrbinary("RETR foo", 1024) } - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_getbinaryfile - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - binary_data.scan(/.{1,1024}/nm) do |s| - conn.print(s) - end - conn.shutdown(Socket::SHUT_WR) - conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = ftp.getbinaryfile("foo", nil) - assert_equal(binary_data, buf) - assert_equal(Encoding::ASCII_8BIT, buf.encoding) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_getbinaryfile_empty - commands = [] - binary_data = "" - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - conn.shutdown(Socket::SHUT_WR) - conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = ftp.getbinaryfile("foo", nil) - assert_equal(binary_data, buf) - assert_equal(Encoding::ASCII_8BIT, buf.encoding) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_getbinaryfile_with_filename_and_block - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - binary_data.scan(/.{1,1024}/nm) do |s| - conn.print(s) - end - conn.shutdown(Socket::SHUT_WR) - conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - Tempfile.create("foo", external_encoding: "ASCII-8BIT") do |f| - f.binmode - buf = String.new - res = ftp.getbinaryfile("foo", f.path) { |s| - buf << s - } - assert_equal(nil, res) - assert_equal(binary_data, buf) - assert_equal(Encoding::ASCII_8BIT, buf.encoding) - assert_equal(binary_data, f.read) - end - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_storbinary - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - stored_data = nil - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo\r\n") - conn = TCPSocket.new(host, port) - stored_data = conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.storbinary("STOR foo", StringIO.new(binary_data), 1024) - assert_equal(binary_data, stored_data) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("STOR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_storbinary_fail - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("452 Requested file action aborted.\r\n") - [host, port] - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_raise(Net::FTPTempError){ ftp.storbinary("STOR foo", StringIO.new(binary_data), 1024) } - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("STOR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_retrlines - commands = [] - text_data = <<EOF.gsub(/\n/, "\r\n") -foo -bar -baz -EOF - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening TEXT mode data connection for foo (#{text_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - text_data.each_line do |l| - conn.print(l) - end - conn.shutdown(Socket::SHUT_WR) - conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = String.new - ftp.retrlines("RETR foo") do |line| - buf << line + "\r\n" - end - assert_equal(text_data.bytesize, buf.bytesize) - assert_equal(text_data, buf) - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_gettextfile - commands = [] - text_data = <<EOF.gsub(/\n/, "\r\n") -foo -bar -baz -EOF - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening TEXT mode data connection for foo (#{text_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - text_data.each_line do |l| - conn.print(l) - end - conn.shutdown(Socket::SHUT_WR) - conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = ftp.gettextfile("foo", nil) - assert_equal(text_data.gsub(/\r\n/, "\n"), buf) - assert_equal(Encoding::ASCII_8BIT, buf.encoding) - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_gettextfile_with_filename_and_block - commands = [] - text_data = <<EOF.gsub(/\n/, "\r\n") -foo -bar -baz -EOF - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening TEXT mode data connection for foo (#{text_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - text_data.each_line do |l| - conn.print(l) - end - conn.shutdown(Socket::SHUT_WR) - conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - Tempfile.create("foo", external_encoding: "ascii-8bit") do |f| - buf = String.new - res = ftp.gettextfile("foo", f.path) { |s| - buf << s << "\n" - } - assert_equal(nil, res) - assert_equal(text_data.gsub(/\r\n/, "\n"), buf) - assert_equal(Encoding::ASCII_8BIT, buf.encoding) - assert_equal(buf, f.read) - end - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_getbinaryfile_in_list - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join - list_lines = [ - "-rw-r--r-- 1 0 0 0 Mar 30 11:22 foo.txt", - "-rw-r--r-- 1 0 0 0 Mar 30 11:22 bar.txt", - "-rw-r--r-- 1 0 0 0 Mar 30 11:22 baz.bin" - ] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Here comes the directory listing.\r\n") - conn = TCPSocket.new(host, port) - list_lines.each_with_index do |l, i| - conn.print(l, "\r\n") - end - conn.close - sock.print("226 Directory send OK.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - conn.print(binary_data) - conn.close - sock.print("226 Transfer complete.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.list do |line| - file = line.slice(/(\S*\.bin)\z/) - if file - data = ftp.getbinaryfile(file, nil) - assert_equal(binary_data, data) - end - end - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("LIST\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR baz.bin\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_abort - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("225 No transfer to ABOR.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.abort - assert_equal("ABOR\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_status - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("211 End of status\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.status - assert_equal("STAT\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_status_path - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("213 End of status\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.status "/" - assert_equal("STAT /\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_pathnames - require 'pathname' - - commands = [] - server = create_ftp_server(0.2) { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("257 'foo' directory created.\r\n") - commands.push(sock.gets) - sock.print("250 CWD command successful.\r\n") - commands.push(sock.gets) - sock.print("250 CWD command successful.\r\n") - commands.push(sock.gets) - sock.print("250 RMD command successful.\r\n") - commands.push(sock.gets) - sock.print("213 test.txt Fri, 11 Jan 2013 11:20:41 -0500.\r\n") - commands.push(sock.gets) - sock.print("213 test.txt 16.\r\n") - commands.push(sock.gets) - sock.print("350 File exists, ready for destination name\r\n") - commands.push(sock.gets) - sock.print("250 RNTO command successful.\r\n") - commands.push(sock.gets) - sock.print("250 DELE command successful.\r\n") - } - - begin - begin - dir = Pathname.new("foo") - file = Pathname.new("test.txt") - file2 = Pathname.new("test2.txt") - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - ftp.mkdir(dir) - ftp.chdir(dir) - ftp.chdir("..") - ftp.rmdir(dir) - ftp.mdtm(file) - ftp.size(file) - ftp.rename(file, file2) - ftp.delete(file) - - # TODO: These commented tests below expose the error but don't test anything: - # TypeError: no implicit conversion of Pathname into String - # ftp.nlst(dir) - # ftp.putbinaryfile(Pathname.new("/etc/hosts"), file2) - # ftp.puttextfile(Pathname.new("/etc/hosts"), file2) - # ftp.gettextfile(Pathname.new("/etc/hosts"), file2) - # ftp.getbinaryfile(Pathname.new("/etc/hosts"), file2) - # ftp.list(dir, dir, dir) - - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_match(/\ATYPE /, commands.shift) - assert_match(/\AMKD /, commands.shift) - assert_match(/\ACWD /, commands.shift) - assert_match(/\ACDUP/, commands.shift) - assert_match(/\ARMD /, commands.shift) - assert_match(/\AMDTM /, commands.shift) - assert_match(/\ASIZE /, commands.shift) - assert_match(/\ARNFR /, commands.shift) - assert_match(/\ARNTO /, commands.shift) - assert_match(/\ADELE /, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_getmultiline - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - sock.print("123- foo\r\n") - sock.print("bar\r\n") - sock.print(" 123 baz\r\n") - sock.print("123 quux\r\n") - sock.print("123 foo\r\n") - sock.print("foo\r\n") - sock.print("\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - assert_equal("123- foo\nbar\n 123 baz\n123 quux\n", - ftp.send(:getmultiline)) - assert_equal("123 foo\n", ftp.send(:getmultiline)) - assert_equal("foo\n", ftp.send(:getmultiline)) - assert_equal("\n", ftp.send(:getmultiline)) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_size - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("213 12345\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - assert_equal(12345, ftp.size("foo.txt")) - assert_match("SIZE foo.txt\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_mdtm - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("213 20150910161739\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - assert_equal("20150910161739", ftp.mdtm("foo.txt")) - assert_match("MDTM foo.txt\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_mtime - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("213 20150910161739\r\n") - commands.push(sock.gets) - sock.print("213 20150910161739\r\n") - commands.push(sock.gets) - sock.print("213 20150910161739.123456\r\n") - commands.push(sock.gets) - sock.print("213 20150910161739.123\r\n") - commands.push(sock.gets) - sock.print("213 20150910161739.123456789\r\n") - commands.push(sock.gets) - sock.print("213 2015091016173\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - assert_equal(Time.utc(2015, 9, 10, 16, 17, 39), ftp.mtime("foo.txt")) - assert_equal(Time.local(2015, 9, 10, 16, 17, 39), - ftp.mtime("foo.txt", true)) - assert_equal(Time.utc(2015, 9, 10, 16, 17, 39, 123456), - ftp.mtime("bar.txt")) - assert_equal(Time.utc(2015, 9, 10, 16, 17, 39, 123000), - ftp.mtime("bar.txt")) - assert_equal(Time.utc(2015, 9, 10, 16, 17, 39, - Rational(123456789, 1000)), - ftp.mtime("bar.txt")) - assert_raise(Net::FTPProtoError) do - ftp.mtime("quux.txt") - end - assert_match("MDTM foo.txt\r\n", commands.shift) - assert_match("MDTM foo.txt\r\n", commands.shift) - assert_match("MDTM bar.txt\r\n", commands.shift) - assert_match("MDTM bar.txt\r\n", commands.shift) - assert_match("MDTM bar.txt\r\n", commands.shift) - assert_match("MDTM quux.txt\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_system - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("215 UNIX Type: L8\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - assert_equal("UNIX Type: L8", ftp.system) - assert_match("SYST\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_mlst - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("250- Listing foo\r\n") - sock.print(" Type=file;Unique=FC00U1E554A;Size=1234567;Modify=20131220035929;Perm=r;Unix.mode=0644;Unix.owner=122;Unix.group=0;Unix.ctime=20131220120140;Unix.atime=20131220131139; /foo\r\n") - sock.print("250 End\r\n") - commands.push(sock.gets) - sock.print("250 Malformed response\r\n") - commands.push(sock.gets) - sock.print("250- Listing foo\r\n") - sock.print("\r\n") - sock.print("250 End\r\n") - commands.push(sock.gets) - sock.print("250- Listing foo\r\n") - sock.print(" abc /foo\r\n") - sock.print("250 End\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - entry = ftp.mlst("foo") - assert_equal("/foo", entry.pathname) - assert_equal("file", entry.facts["type"]) - assert_equal("FC00U1E554A", entry.facts["unique"]) - assert_equal(1234567, entry.facts["size"]) - assert_equal("r", entry.facts["perm"]) - assert_equal(0644, entry.facts["unix.mode"]) - assert_equal(122, entry.facts["unix.owner"]) - assert_equal(0, entry.facts["unix.group"]) - modify = entry.facts["modify"] - assert_equal(2013, modify.year) - assert_equal(12, modify.month) - assert_equal(20, modify.day) - assert_equal(3, modify.hour) - assert_equal(59, modify.min) - assert_equal(29, modify.sec) - assert_equal(true, modify.utc?) - ctime = entry.facts["unix.ctime"] - assert_equal(12, ctime.hour) - assert_equal(1, ctime.min) - assert_equal(40, ctime.sec) - atime = entry.facts["unix.atime"] - assert_equal(13, atime.hour) - assert_equal(11, atime.min) - assert_equal(39, atime.sec) - assert_match("MLST foo\r\n", commands.shift) - assert_raise(Net::FTPProtoError) do - ftp.mlst("foo") - end - assert_match("MLST foo\r\n", commands.shift) - assert_raise(Net::FTPProtoError) do - ftp.mlst("foo") - end - assert_match("MLST foo\r\n", commands.shift) - entry = ftp.mlst("foo") - assert_equal("/foo", entry.pathname) - assert_match("MLST foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_mlsd - commands = [] - entry_lines = [ - "Type=file;Unique=FC00U1E554A;Size=1234567;Modify=20131220035929.123456;Perm=r; foo bar", - "Type=cdir;Unique=FC00U1E554B;Modify=20131220035929;Perm=flcdmpe; .", - "Type=pdir;Unique=FC00U1E554C;Modify=20131220035929;Perm=flcdmpe; ..", - ] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Here comes the directory listing.\r\n") - begin - conn = TCPSocket.new(host, port) - entry_lines.each do |l| - conn.print(l, "\r\n") - end - rescue Errno::EPIPE - ensure - assert_nil($!) - conn.close - end - sock.print("226 Directory send OK.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - entries = ftp.mlsd("/") - assert_equal(3, entries.size) - assert_equal("foo bar", entries[0].pathname) - assert_equal(".", entries[1].pathname) - assert_equal("..", entries[2].pathname) - assert_equal("file", entries[0].facts["type"]) - assert_equal("cdir", entries[1].facts["type"]) - assert_equal("pdir", entries[2].facts["type"]) - assert_equal("flcdmpe", entries[1].facts["perm"]) - modify = entries[0].facts["modify"] - assert_equal(2013, modify.year) - assert_equal(12, modify.month) - assert_equal(20, modify.day) - assert_equal(3, modify.hour) - assert_equal(59, modify.min) - assert_equal(29, modify.sec) - assert_equal(123456, modify.usec) - assert_equal(true, modify.utc?) - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_match("MLSD /\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_parse257 - ftp = Net::FTP.new - assert_equal('/foo/bar', - ftp.send(:parse257, '257 "/foo/bar" directory created')) - assert_equal('/foo/bar"baz', - ftp.send(:parse257, '257 "/foo/bar""baz" directory created')) - assert_equal('/foo/x"y"z', - ftp.send(:parse257, '257 "/foo/x""y""z" directory created')) - assert_equal('/foo/bar', - ftp.send(:parse257, '257 "/foo/bar" "comment"')) - assert_equal('', - ftp.send(:parse257, '257 "" directory created')) - assert_equal('', - ftp.send(:parse257, '257 directory created')) - assert_raise(Net::FTPReplyError) do - ftp.send(:parse257, "500 Syntax error") - end - end - - def test_putline_reject_crlf - ftp = Net::FTP.new - assert_raise(ArgumentError) do - ftp.send(:putline, "\r") - end - assert_raise(ArgumentError) do - ftp.send(:putline, "\n") - end - end - - if defined?(OpenSSL::SSL) - def test_tls_unknown_ca - assert_raise(OpenSSL::SSL::SSLError) do - tls_test do |port| - begin - Net::FTP.new(SERVER_NAME, - :port => port, - :ssl => true) - rescue SystemCallError - skip $! - end - end - end - end - - def test_tls_with_ca_file - assert_nothing_raised do - tls_test do |port| - begin - Net::FTP.new(SERVER_NAME, - :port => port, - :ssl => { :ca_file => CA_FILE }) - rescue SystemCallError - skip $! - end - end - end - end - - def test_tls_verify_none - assert_nothing_raised do - tls_test do |port| - Net::FTP.new(SERVER_ADDR, - :port => port, - :ssl => { :verify_mode => OpenSSL::SSL::VERIFY_NONE }) - end - end - end - - def test_tls_post_connection_check - assert_raise(OpenSSL::SSL::SSLError) do - tls_test do |port| - # SERVER_ADDR is different from the hostname in the certificate, - # so the following code should raise a SSLError. - Net::FTP.new(SERVER_ADDR, - :port => port, - :ssl => { :ca_file => CA_FILE }) - end - end - end - - def test_active_private_data_connection - server = TCPServer.new(SERVER_ADDR, 0) - port = server.addr[1] - commands = [] - session_reused_for_data_connection = nil - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - @thread = Thread.start do - sock = server.accept - begin - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("234 AUTH success.\r\n") - ctx = OpenSSL::SSL::SSLContext.new - ctx.ca_file = CA_FILE - ctx.key = File.open(SERVER_KEY) { |f| - OpenSSL::PKey::RSA.new(f) - } - ctx.cert = File.open(SERVER_CERT) { |f| - OpenSSL::X509::Certificate.new(f) - } - sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) - sock.sync_close = true - begin - sock.accept - commands.push(sock.gets) - sock.print("200 PSBZ success.\r\n") - commands.push(sock.gets) - sock.print("200 PROT success.\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - conn = OpenSSL::SSL::SSLSocket.new(conn, ctx) - conn.sync_close = true - conn.accept - session_reused_for_data_connection = conn.session_reused? - binary_data.scan(/.{1,1024}/nm) do |s| - conn.print(s) - end - conn.close - sock.print("226 Transfer complete.\r\n") - rescue OpenSSL::SSL::SSLError - end - ensure - sock.close - server.close - end - end - ftp = Net::FTP.new(SERVER_NAME, - port: port, - ssl: { ca_file: CA_FILE }, - passive: false) - begin - assert_equal("AUTH TLS\r\n", commands.shift) - assert_equal("PBSZ 0\r\n", commands.shift) - assert_equal("PROT P\r\n", commands.shift) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = ftp.getbinaryfile("foo", nil) - assert_equal(binary_data, buf) - assert_equal(Encoding::ASCII_8BIT, buf.encoding) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. - # See https://github.com/openssl/openssl/pull/5967 for details. - if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/ - assert_equal(true, session_reused_for_data_connection) - end - ensure - ftp.close - end - end - - def test_passive_private_data_connection - server = TCPServer.new(SERVER_ADDR, 0) - port = server.addr[1] - commands = [] - session_reused_for_data_connection = nil - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - @thread = Thread.start do - sock = server.accept - begin - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("234 AUTH success.\r\n") - ctx = OpenSSL::SSL::SSLContext.new - ctx.ca_file = CA_FILE - ctx.key = File.open(SERVER_KEY) { |f| - OpenSSL::PKey::RSA.new(f) - } - ctx.cert = File.open(SERVER_CERT) { |f| - OpenSSL::X509::Certificate.new(f) - } - sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) - sock.sync_close = true - begin - sock.accept - commands.push(sock.gets) - sock.print("200 PSBZ success.\r\n") - commands.push(sock.gets) - sock.print("200 PROT success.\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - data_server = create_data_connection_server(sock) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = data_server.accept - conn = OpenSSL::SSL::SSLSocket.new(conn, ctx) - conn.sync_close = true - conn.accept - session_reused_for_data_connection = conn.session_reused? - binary_data.scan(/.{1,1024}/nm) do |s| - conn.print(s) - end - conn.close - data_server.close - sock.print("226 Transfer complete.\r\n") - rescue OpenSSL::SSL::SSLError - end - ensure - sock.close - server.close - end - end - ftp = Net::FTP.new(SERVER_NAME, - port: port, - ssl: { ca_file: CA_FILE }, - passive: true) - begin - assert_equal("AUTH TLS\r\n", commands.shift) - assert_equal("PBSZ 0\r\n", commands.shift) - assert_equal("PROT P\r\n", commands.shift) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = ftp.getbinaryfile("foo", nil) - assert_equal(binary_data, buf) - assert_equal(Encoding::ASCII_8BIT, buf.encoding) - assert_match(/\A(PASV|EPSV)\r\n/, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. - if OpenSSL::OPENSSL_LIBRARY_VERSION !~ /OpenSSL 1.1.0h/ - assert_equal(true, session_reused_for_data_connection) - end - ensure - ftp.close - end - end - - def test_active_clear_data_connection - server = TCPServer.new(SERVER_ADDR, 0) - port = server.addr[1] - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - @thread = Thread.start do - sock = server.accept - begin - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("234 AUTH success.\r\n") - ctx = OpenSSL::SSL::SSLContext.new - ctx.ca_file = CA_FILE - ctx.key = File.open(SERVER_KEY) { |f| - OpenSSL::PKey::RSA.new(f) - } - ctx.cert = File.open(SERVER_CERT) { |f| - OpenSSL::X509::Certificate.new(f) - } - sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) - sock.sync_close = true - begin - sock.accept - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - binary_data.scan(/.{1,1024}/nm) do |s| - conn.print(s) - end - conn.close - sock.print("226 Transfer complete.\r\n") - rescue OpenSSL::SSL::SSLError - end - ensure - sock.close - server.close - end - end - ftp = Net::FTP.new(SERVER_NAME, - port: port, - ssl: { ca_file: CA_FILE }, - private_data_connection: false, - passive: false) - begin - assert_equal("AUTH TLS\r\n", commands.shift) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = ftp.getbinaryfile("foo", nil) - assert_equal(binary_data, buf) - assert_equal(Encoding::ASCII_8BIT, buf.encoding) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close - end - end - - def test_passive_clear_data_connection - server = TCPServer.new(SERVER_ADDR, 0) - port = server.addr[1] - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - @thread = Thread.start do - sock = server.accept - begin - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("234 AUTH success.\r\n") - ctx = OpenSSL::SSL::SSLContext.new - ctx.ca_file = CA_FILE - ctx.key = File.open(SERVER_KEY) { |f| - OpenSSL::PKey::RSA.new(f) - } - ctx.cert = File.open(SERVER_CERT) { |f| - OpenSSL::X509::Certificate.new(f) - } - sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) - sock.sync_close = true - begin - sock.accept - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - data_server = create_data_connection_server(sock) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for foo (#{binary_data.size} bytes)\r\n") - conn = data_server.accept - binary_data.scan(/.{1,1024}/nm) do |s| - conn.print(s) - end - conn.close - data_server.close - sock.print("226 Transfer complete.\r\n") - rescue OpenSSL::SSL::SSLError - end - ensure - sock.close - server.close - end - end - ftp = Net::FTP.new(SERVER_NAME, - port: port, - ssl: { ca_file: CA_FILE }, - private_data_connection: false, - passive: true) - begin - assert_equal("AUTH TLS\r\n", commands.shift) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - buf = ftp.getbinaryfile("foo", nil) - assert_equal(binary_data, buf) - assert_equal(Encoding::ASCII_8BIT, buf.encoding) - assert_match(/\A(PASV|EPSV)\r\n/, commands.shift) - assert_equal("RETR foo\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close - end - end - - def test_tls_connect_timeout - server = TCPServer.new(SERVER_ADDR, 0) - port = server.addr[1] - commands = [] - sock = nil - @thread = Thread.start do - sock = server.accept - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("234 AUTH success.\r\n") - end - begin - assert_raise(Net::OpenTimeout) do - Net::FTP.new(SERVER_NAME, - port: port, - ssl: { ca_file: CA_FILE }, - ssl_handshake_timeout: 0.1) - end - @thread.join - ensure - sock.close if sock - server.close - end - end - end - - def test_abort_tls - return unless defined?(OpenSSL) - - commands = [] - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("234 AUTH success.\r\n") - ctx = OpenSSL::SSL::SSLContext.new - ctx.ca_file = CA_FILE - ctx.key = File.open(SERVER_KEY) { |f| - OpenSSL::PKey::RSA.new(f) - } - ctx.cert = File.open(SERVER_CERT) { |f| - OpenSSL::X509::Certificate.new(f) - } - sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) - sock.sync_close = true - sock.accept - commands.push(sock.gets) - sock.print("200 PSBZ success.\r\n") - commands.push(sock.gets) - sock.print("200 PROT success.\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("225 No transfer to ABOR.\r\n") - } - begin - begin - ftp = Net::FTP.new(SERVER_NAME, - port: server.port, - ssl: { ca_file: CA_FILE }) - assert_equal("AUTH TLS\r\n", commands.shift) - assert_equal("PBSZ 0\r\n", commands.shift) - assert_equal("PROT P\r\n", commands.shift) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.abort - assert_equal("ABOR\r\n", commands.shift) - assert_equal(nil, commands.shift) - rescue RuntimeError, LoadError - # skip (require openssl) - ensure - ftp.close if ftp - end - ensure - server.close - end - end - - def test_getbinaryfile_command_injection - skip "| is not allowed in filename on Windows" if windows? - [false, true].each do |resume| - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for |echo hello (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - binary_data.scan(/.{1,1024}/nm) do |s| - conn.print(s) - end - conn.shutdown(Socket::SHUT_WR) - conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - } - begin - chdir_to_tmpdir do - begin - ftp = Net::FTP.new - ftp.resume = resume - ftp.read_timeout = RubyVM::MJIT.enabled? ? 5 : 0.2 # use large timeout for --jit-wait - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.getbinaryfile("|echo hello") - assert_equal(binary_data, File.binread("./|echo hello")) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR |echo hello\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - end - ensure - server.close - end - end - end - - def test_gettextfile_command_injection - skip "| is not allowed in filename on Windows" if windows? - commands = [] - text_data = <<EOF.gsub(/\n/, "\r\n") -foo -bar -baz -EOF - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening TEXT mode data connection for |echo hello (#{text_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - text_data.each_line do |l| - conn.print(l) - end - conn.shutdown(Socket::SHUT_WR) - conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - chdir_to_tmpdir do - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.gettextfile("|echo hello") - assert_equal(text_data.gsub(/\r\n/, "\n"), - File.binread("./|echo hello")) - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("RETR |echo hello\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - end - ensure - server.close - end - end - - def test_putbinaryfile_command_injection - skip "| is not allowed in filename on Windows" if windows? - commands = [] - binary_data = (0..0xff).map {|i| i.chr}.join * 4 * 3 - received_data = nil - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening BINARY mode data connection for |echo hello (#{binary_data.size} bytes)\r\n") - conn = TCPSocket.new(host, port) - received_data = conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - } - begin - chdir_to_tmpdir do - File.binwrite("./|echo hello", binary_data) - begin - ftp = Net::FTP.new - ftp.read_timeout = 0.2 - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.putbinaryfile("|echo hello") - assert_equal(binary_data, received_data) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("STOR |echo hello\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - end - ensure - server.close - end - end - - def test_puttextfile_command_injection - skip "| is not allowed in filename on Windows" if windows? - commands = [] - received_data = nil - server = create_ftp_server { |sock| - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("331 Please specify the password.\r\n") - commands.push(sock.gets) - sock.print("230 Login successful.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to ASCII mode.\r\n") - line = sock.gets - commands.push(line) - host, port = process_port_or_eprt(sock, line) - commands.push(sock.gets) - sock.print("150 Opening TEXT mode data connection for |echo hello\r\n") - conn = TCPSocket.new(host, port) - received_data = conn.read - conn.close - sock.print("226 Transfer complete.\r\n") - commands.push(sock.gets) - sock.print("200 Switching to Binary mode.\r\n") - } - begin - chdir_to_tmpdir do - File.open("|echo hello", "w") do |f| - f.puts("foo") - f.puts("bar") - f.puts("baz") - end - begin - ftp = Net::FTP.new - ftp.connect(SERVER_ADDR, server.port) - ftp.login - assert_match(/\AUSER /, commands.shift) - assert_match(/\APASS /, commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - ftp.puttextfile("|echo hello") - assert_equal(<<EOF.gsub(/\n/, "\r\n"), received_data) -foo -bar -baz -EOF - assert_equal("TYPE A\r\n", commands.shift) - assert_match(/\A(PORT|EPRT) /, commands.shift) - assert_equal("STOR |echo hello\r\n", commands.shift) - assert_equal("TYPE I\r\n", commands.shift) - assert_equal(nil, commands.shift) - ensure - ftp.close if ftp - end - end - ensure - server.close - end - end - - private - - def create_ftp_server(sleep_time = nil) - server = TCPServer.new(SERVER_ADDR, 0) - @thread = Thread.start do - if sleep_time - sleep(sleep_time) - end - sock = server.accept - begin - sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_OOBINLINE, 1) - yield(sock) - sock.shutdown(Socket::SHUT_WR) - sock.read unless sock.eof? - ensure - sock.close - end - end - def server.port - addr[1] - end - return server - end - - def tls_test - server = TCPServer.new(SERVER_ADDR, 0) - port = server.addr[1] - commands = [] - @thread = Thread.start do - sock = server.accept - begin - sock.print("220 (test_ftp).\r\n") - commands.push(sock.gets) - sock.print("234 AUTH success.\r\n") - ctx = OpenSSL::SSL::SSLContext.new - ctx.ca_file = CA_FILE - ctx.key = File.open(SERVER_KEY) { |f| - OpenSSL::PKey::RSA.new(f) - } - ctx.cert = File.open(SERVER_CERT) { |f| - OpenSSL::X509::Certificate.new(f) - } - sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) - sock.sync_close = true - begin - sock.accept - commands.push(sock.gets) - sock.print("200 PSBZ success.\r\n") - commands.push(sock.gets) - sock.print("200 PROT success.\r\n") - rescue OpenSSL::SSL::SSLError, SystemCallError - end - ensure - sock.close - server.close - end - end - ftp = yield(port) - ftp.close - - assert_equal("AUTH TLS\r\n", commands.shift) - assert_equal("PBSZ 0\r\n", commands.shift) - assert_equal("PROT P\r\n", commands.shift) - end - - def process_port_or_eprt(sock, line) - case line - when /\APORT (.*)/ - port_args = $1.split(/,/) - host = port_args[0, 4].join(".") - port = port_args[4, 2].map(&:to_i).inject {|x, y| (x << 8) + y} - sock.print("200 PORT command successful.\r\n") - return host, port - when /\AEPRT \|2\|(.*?)\|(.*?)\|/ - host = $1 - port = $2.to_i - sock.print("200 EPRT command successful.\r\n") - return host, port - else - flunk "PORT or EPRT expected" - end - end - - def create_data_connection_server(sock) - data_server = TCPServer.new(SERVER_ADDR, 0) - port = data_server.local_address.ip_port - if data_server.local_address.ipv4? - sock.printf("227 Entering Passive Mode (127,0,0,1,%s).\r\n", - port.divmod(256).join(",")) - elsif data_server.local_address.ipv6? - sock.printf("229 Entering Extended Passive Mode (|||%d|)\r\n", port) - else - flunk "Invalid local address" - end - return data_server - end - - def chdir_to_tmpdir - Dir.mktmpdir do |dir| - pwd = Dir.pwd - Dir.chdir(dir) - begin - yield - ensure - Dir.chdir(pwd) - end - end - end -end diff --git a/test/net/ftp/test_mlsx_entry.rb b/test/net/ftp/test_mlsx_entry.rb deleted file mode 100644 index 92fe411548..0000000000 --- a/test/net/ftp/test_mlsx_entry.rb +++ /dev/null @@ -1,98 +0,0 @@ -# frozen_string_literal: true - -require "net/ftp" -require "test/unit" -require "ostruct" -require "stringio" - -class MLSxEntryTest < Test::Unit::TestCase - def test_file? - assert_equal(true, Net::FTP::MLSxEntry.new({"type"=>"file"}, "foo").file?) - assert_equal(false, Net::FTP::MLSxEntry.new({"type"=>"dir"}, "foo").file?) - assert_equal(false, Net::FTP::MLSxEntry.new({"type"=>"cdir"}, "foo").file?) - assert_equal(false, Net::FTP::MLSxEntry.new({"type"=>"pdir"}, "foo").file?) - end - - def test_directory? - assert_equal(false, - Net::FTP::MLSxEntry.new({"type"=>"file"}, "foo").directory?) - assert_equal(true, - Net::FTP::MLSxEntry.new({"type"=>"dir"}, "foo").directory?) - assert_equal(true, - Net::FTP::MLSxEntry.new({"type"=>"cdir"}, "foo").directory?) - assert_equal(true, - Net::FTP::MLSxEntry.new({"type"=>"pdir"}, "foo").directory?) - end - - def test_appendable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"a"}, "foo").appendable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo").appendable?) - end - - def test_creatable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"c"}, "foo").creatable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo").creatable?) - end - - def test_deletable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"d"}, "foo").deletable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo").deletable?) - end - - def test_enterable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"e"}, "foo").enterable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo").enterable?) - end - - def test_renamable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"f"}, "foo").renamable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo").renamable?) - end - - def test_listable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"l"}, "foo").listable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo").listable?) - end - - def test_directory_makable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"m"}, "foo"). - directory_makable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo"). - directory_makable?) - end - - def test_purgeable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"p"}, "foo").purgeable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo").purgeable?) - end - - def test_readable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"r"}, "foo").readable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo").readable?) - end - - def test_writable? - assert_equal(true, - Net::FTP::MLSxEntry.new({"perm"=>"w"}, "foo").writable?) - assert_equal(false, - Net::FTP::MLSxEntry.new({"perm"=>""}, "foo").writable?) - end -end diff --git a/test/net/http/test_http.rb b/test/net/http/test_http.rb index 0344ad786b..4e7fa22756 100644 --- a/test/net/http/test_http.rb +++ b/test/net/http/test_http.rb @@ -34,7 +34,7 @@ class TestNetHTTP < Test::Unit::TestCase end def test_class_Proxy_from_ENV - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do ENV['http_proxy'] = 'http://proxy.example:8000' # These are ignored on purpose. See Bug 4388 and Feature 6546 @@ -115,7 +115,7 @@ class TestNetHTTP < Test::Unit::TestCase end def test_proxy_address - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do http = Net::HTTP.new 'hostname.example', nil, 'proxy.example' assert_equal 'proxy.example', http.proxy_address @@ -125,17 +125,17 @@ class TestNetHTTP < Test::Unit::TestCase end def test_proxy_address_no_proxy - clean_http_proxy_env do - http = Net::HTTP.new 'hostname.example', nil, 'proxy.example', nil, nil, nil, 'example' + TestNetHTTPUtils.clean_http_proxy_env do + http = Net::HTTP.new 'hostname.example', nil, 'proxy.com', nil, nil, nil, 'example' assert_nil http.proxy_address - http = Net::HTTP.new '10.224.1.1', nil, 'proxy.example', nil, nil, nil, 'example,10.224.0.0/22' + http = Net::HTTP.new '10.224.1.1', nil, 'proxy.com', nil, nil, nil, 'example,10.224.0.0/22' assert_nil http.proxy_address end end def test_proxy_from_env_ENV - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do ENV['http_proxy'] = 'http://proxy.example:8000' assert_equal false, Net::HTTP.proxy_class? @@ -146,7 +146,7 @@ class TestNetHTTP < Test::Unit::TestCase end def test_proxy_address_ENV - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do ENV['http_proxy'] = 'http://proxy.example:8000' http = Net::HTTP.new 'hostname.example' @@ -156,13 +156,13 @@ class TestNetHTTP < Test::Unit::TestCase end def test_proxy_eh_no_proxy - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do assert_equal false, Net::HTTP.new('hostname.example', nil, nil).proxy? end end def test_proxy_eh_ENV - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do ENV['http_proxy'] = 'http://proxy.example:8000' http = Net::HTTP.new 'hostname.example' @@ -172,30 +172,37 @@ class TestNetHTTP < Test::Unit::TestCase end def test_proxy_eh_ENV_with_user - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do ENV['http_proxy'] = 'http://foo:bar@proxy.example:8000' http = Net::HTTP.new 'hostname.example' assert_equal true, http.proxy? - if Net::HTTP::ENVIRONMENT_VARIABLE_IS_MULTIUSER_SAFE - assert_equal 'foo', http.proxy_user - assert_equal 'bar', http.proxy_pass - else - assert_nil http.proxy_user - assert_nil http.proxy_pass - end + assert_equal 'foo', http.proxy_user + assert_equal 'bar', http.proxy_pass + end + end + + def test_proxy_eh_ENV_with_urlencoded_user + TestNetHTTPUtils.clean_http_proxy_env do + ENV['http_proxy'] = 'http://Y%5CX:R%25S%5D%20%3FX@proxy.example:8000' + + http = Net::HTTP.new 'hostname.example' + + assert_equal true, http.proxy? + assert_equal "Y\\X", http.proxy_user + assert_equal "R%S] ?X", http.proxy_pass end end def test_proxy_eh_ENV_none_set - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do assert_equal false, Net::HTTP.new('hostname.example').proxy? end end def test_proxy_eh_ENV_no_proxy - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do ENV['http_proxy'] = 'http://proxy.example:8000' ENV['no_proxy'] = 'hostname.example' @@ -204,7 +211,7 @@ class TestNetHTTP < Test::Unit::TestCase end def test_proxy_port - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do http = Net::HTTP.new 'example', nil, 'proxy.example' assert_equal 'proxy.example', http.proxy_address assert_equal 80, http.proxy_port @@ -216,7 +223,7 @@ class TestNetHTTP < Test::Unit::TestCase end def test_proxy_port_ENV - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do ENV['http_proxy'] = 'http://proxy.example:8000' http = Net::HTTP.new 'hostname.example' @@ -226,7 +233,7 @@ class TestNetHTTP < Test::Unit::TestCase end def test_newobj - clean_http_proxy_env do + TestNetHTTPUtils.clean_http_proxy_env do ENV['http_proxy'] = 'http://proxy.example:8000' http = Net::HTTP.newobj 'hostname.example' @@ -235,25 +242,6 @@ class TestNetHTTP < Test::Unit::TestCase end end - def clean_http_proxy_env - orig = { - 'http_proxy' => ENV['http_proxy'], - 'http_proxy_user' => ENV['http_proxy_user'], - 'http_proxy_pass' => ENV['http_proxy_pass'], - 'no_proxy' => ENV['no_proxy'], - } - - orig.each_key do |key| - ENV.delete key - end - - yield - ensure - orig.each do |key, value| - ENV[key] = value - end - end - def test_failure_message_includes_failed_domain_and_port # hostname to be included in the error message host = Struct.new(:to_s).new("<example>") @@ -262,10 +250,22 @@ class TestNetHTTP < Test::Unit::TestCase def host.to_str; raise SocketError, "open failure"; end uri = Struct.new(:scheme, :hostname, :port).new("http", host, port) assert_raise_with_message(SocketError, /#{host}:#{port}/) do - clean_http_proxy_env{ Net::HTTP.get(uri) } + TestNetHTTPUtils.clean_http_proxy_env{ Net::HTTP.get(uri) } end end + def test_default_configuration + Net::HTTP.default_configuration = { open_timeout: 5 } + http = Net::HTTP.new 'hostname.example' + assert_equal 5, http.open_timeout + assert_equal 60, http.read_timeout + + http.open_timeout = 10 + assert_equal 10, http.open_timeout + ensure + Net::HTTP.default_configuration = nil + end + end module TestNetHTTP_version_1_1_methods @@ -302,6 +302,27 @@ module TestNetHTTP_version_1_1_methods def test_s_get assert_equal $test_net_http_data, Net::HTTP.get(config('host'), '/', config('port')) + + assert_equal $test_net_http_data, Net::HTTP.get( + URI.parse("http://#{config('host')}:#{config('port')}") + ) + assert_equal $test_net_http_data, Net::HTTP.get( + URI.parse("http://#{config('host')}:#{config('port')}"), "Accept" => "text/plain" + ) + end + + def test_s_get_response + res = Net::HTTP.get_response( + URI.parse("http://#{config('host')}:#{config('port')}") + ) + assert_equal "application/octet-stream", res["Content-Type"] + assert_equal $test_net_http_data, res.body + + res = Net::HTTP.get_response( + URI.parse("http://#{config('host')}:#{config('port')}"), "Accept" => "text/plain" + ) + assert_equal "text/plain", res["Content-Type"] + assert_equal $test_net_http_data, res.body end def test_head @@ -433,7 +454,11 @@ module TestNetHTTP_version_1_1_methods def test_post start {|http| _test_post__base http + } + start {|http| _test_post__file http + } + start {|http| _test_post__no_data http } end @@ -470,9 +495,9 @@ module TestNetHTTP_version_1_1_methods def test_s_post url = "http://#{config('host')}:#{config('port')}/?q=a" res = Net::HTTP.post( - URI.parse(url), - "a=x") - assert_equal "application/x-www-form-urlencoded", res["Content-Type"] + URI.parse(url), + "a=x") + assert_equal "application/octet-stream", res["Content-Type"] assert_equal "a=x", res.body assert_equal url, res["X-request-uri"] @@ -538,11 +563,13 @@ module TestNetHTTP_version_1_1_methods conn = Net::HTTP.new('localhost', port) conn.write_timeout = EnvUtil.apply_timeout_scale(0.01) conn.read_timeout = EnvUtil.apply_timeout_scale(0.01) if windows? - conn.open_timeout = EnvUtil.apply_timeout_scale(0.1) + conn.open_timeout = EnvUtil.apply_timeout_scale(1) th = Thread.new do err = !windows? ? Net::WriteTimeout : Net::ReadTimeout - assert_raise(err) { conn.post('/', "a"*50_000_000) } + assert_raise(err) do + conn.post('/', "a"*50_000_000) + end end assert th.join(EnvUtil.apply_timeout_scale(10)) } @@ -551,6 +578,33 @@ module TestNetHTTP_version_1_1_methods th&.join end + def test_timeout_during_non_chunked_streamed_HTTP_session_write + th = nil + # listen for connections... but deliberately do not read + TCPServer.open('localhost', 0) {|server| + port = server.addr[1] + + conn = Net::HTTP.new('localhost', port) + conn.write_timeout = EnvUtil.apply_timeout_scale(0.01) + conn.read_timeout = EnvUtil.apply_timeout_scale(0.01) if windows? + conn.open_timeout = EnvUtil.apply_timeout_scale(1) + + req = Net::HTTP::Post.new('/') + data = "a"*50_000_000 + req.content_length = data.size + req['Content-Type'] = 'application/x-www-form-urlencoded' + req.body_stream = StringIO.new(data) + + th = Thread.new do + assert_raise(Net::WriteTimeout) { conn.request(req) } + end + assert th.join(10) + } + ensure + th&.kill + th&.join + end + def test_timeout_during_HTTP_session bug4246 = "expected the HTTP session to have timed out but have not. c.f. [ruby-core:34203]" @@ -561,7 +615,7 @@ module TestNetHTTP_version_1_1_methods conn = Net::HTTP.new('localhost', port) conn.read_timeout = EnvUtil.apply_timeout_scale(0.01) - conn.open_timeout = EnvUtil.apply_timeout_scale(0.1) + conn.open_timeout = EnvUtil.apply_timeout_scale(1) th = Thread.new do assert_raise(Net::ReadTimeout) { @@ -587,10 +641,12 @@ module TestNetHTTP_version_1_2_methods # _test_request__range http # WEBrick does not support Range: header. _test_request__HEAD http _test_request__POST http - _test_request__stream_body http _test_request__uri http _test_request__uri_host http } + start {|http| + _test_request__stream_body http + } end def _test_request__GET(http) @@ -801,7 +857,13 @@ Content-Type: application/octet-stream __EOM__ start {|http| _test_set_form_urlencoded(http, data.reject{|k,v|!v.is_a?(String)}) + } + start {|http| + @server.mount('/', lambda {|req, res| res.body = req.body }) _test_set_form_multipart(http, false, data, expected) + } + start {|http| + @server.mount('/', lambda {|req, res| res.body = req.body }) _test_set_form_multipart(http, true, data, expected) } } @@ -845,6 +907,7 @@ __EOM__ expected.sub!(/<filename>/, filename) expected.sub!(/<data>/, $test_net_http_data) start {|http| + @server.mount('/', lambda {|req, res| res.body = req.body }) data.each{|k,v|v.rewind rescue nil} req = Net::HTTP::Post.new('/') req.set_form(data, 'multipart/form-data') @@ -860,10 +923,11 @@ __EOM__ header) assert_equal(expected, body) - data.each{|k,v|v.rewind rescue nil} - req['Transfer-Encoding'] = 'chunked' - res = http.request req - #assert_equal(expected, res.body) + # TODO: test with chunked + # data.each{|k,v|v.rewind rescue nil} + # req['Transfer-Encoding'] = 'chunked' + # res = http.request req + # assert_equal(expected, res.body) } } end @@ -884,6 +948,17 @@ class TestNetHTTP_v1_2 < Test::Unit::TestCase Net::HTTP.version_1_2 super end + + def test_send_large_POST_request + start {|http| + data = ' '*6000000 + res = http.send_request('POST', '/', data, 'content-type' => 'application/x-www-form-urlencoded') + assert_kind_of Net::HTTPResponse, res + assert_kind_of String, res.body + assert_equal data.size, res.body.size + assert_equal data, res.body + } + end end class TestNetHTTP_v1_2_chunked < Test::Unit::TestCase @@ -931,7 +1006,7 @@ class TestNetHTTPContinue < Test::Unit::TestCase end def mount_proc(&block) - @server.mount('/continue', WEBrick::HTTPServlet::ProcHandler.new(block.to_proc)) + @server.mount('/continue', block.to_proc) end def test_expect_continue @@ -986,10 +1061,10 @@ class TestNetHTTPContinue < Test::Unit::TestCase def test_expect_continue_error_before_body @log_tester = nil mount_proc {|req, res| - raise WEBrick::HTTPStatus::Forbidden + raise TestNetHTTPUtils::Forbidden } start {|http| - uheader = {'content-length' => '5', 'expect' => '100-continue'} + uheader = {'content-type' => 'application/x-www-form-urlencoded', 'content-length' => '5', 'expect' => '100-continue'} http.continue_timeout = 1 # allow the server to respond before sending http.request_post('/continue', 'data', uheader) {|res| assert_equal(res.code, '403') @@ -1031,7 +1106,7 @@ class TestNetHTTPSwitchingProtocols < Test::Unit::TestCase end def mount_proc(&block) - @server.mount('/continue', WEBrick::HTTPServlet::ProcHandler.new(block.to_proc)) + @server.mount('/continue', block.to_proc) end def test_info @@ -1041,7 +1116,8 @@ class TestNetHTTPSwitchingProtocols < Test::Unit::TestCase } start {|http| http.continue_timeout = 0.2 - http.request_post('/continue', 'body=BODY') {|res| + http.request_post('/continue', 'body=BODY', + 'content-type' => 'application/x-www-form-urlencoded') {|res| assert_equal('BODY', res.read_body) } } @@ -1104,6 +1180,30 @@ class TestNetHTTPKeepAlive < Test::Unit::TestCase } end + def test_keep_alive_reset_on_new_connection + # Using debug log output on accepting connection: + # + # "[2021-04-29 20:36:46] DEBUG accept: 127.0.0.1:50674\n" + @log_tester = nil + @logger_level = :debug + + start {|http| + res = http.get('/') + http.keep_alive_timeout = 1 + assert_kind_of Net::HTTPResponse, res + assert_kind_of String, res.body + http.finish + assert_equal 1, @log.grep(/accept/i).size + + sleep 1.5 + http.start + res = http.get('/') + assert_kind_of Net::HTTPResponse, res + assert_kind_of String, res.body + assert_equal 2, @log.grep(/accept/i).size + } + end + class MockSocket attr_reader :count def initialize(success_after: nil) @@ -1156,6 +1256,16 @@ class TestNetHTTPKeepAlive < Test::Unit::TestCase } end + def test_http_retry_failed_with_block + start {|http| + http.max_retries = 10 + called = 0 + assert_raise(Errno::ECONNRESET){ http.get('/'){called += 1; raise Errno::ECONNRESET} } + assert_equal 1, called + } + @log_tester = nil + end + def test_keep_alive_server_close def @server.run(sock) sock.close @@ -1206,3 +1316,112 @@ class TestNetHTTPLocalBind < Test::Unit::TestCase end end +class TestNetHTTPForceEncoding < Test::Unit::TestCase + CONFIG = { + 'host' => 'localhost', + 'proxy_host' => nil, + 'proxy_port' => nil, + } + + include TestNetHTTPUtils + + def fe_request(force_enc, content_type=nil) + @server.mount_proc('/fe') do |req, res| + res['Content-Type'] = content_type if content_type + res.body = "hello\u1234" + end + + http = Net::HTTP.new(config('host'), config('port')) + http.local_host = Addrinfo.tcp(config('host'), config('port')).ip_address + assert_not_nil(http.local_host) + assert_nil(http.local_port) + + http.response_body_encoding = force_enc + http.get('/fe') + end + + def test_response_body_encoding_false + res = fe_request(false) + assert_equal("hello\u1234".b, res.body) + assert_equal(Encoding::ASCII_8BIT, res.body.encoding) + end + + def test_response_body_encoding_true_without_content_type + res = fe_request(true) + assert_equal("hello\u1234".b, res.body) + assert_equal(Encoding::ASCII_8BIT, res.body.encoding) + end + + def test_response_body_encoding_true_with_content_type + res = fe_request(true, 'text/html; charset=utf-8') + assert_equal("hello\u1234", res.body) + assert_equal(Encoding::UTF_8, res.body.encoding) + end + + def test_response_body_encoding_string_without_content_type + res = fe_request('utf-8') + assert_equal("hello\u1234", res.body) + assert_equal(Encoding::UTF_8, res.body.encoding) + end + + def test_response_body_encoding_encoding_without_content_type + res = fe_request(Encoding::UTF_8) + assert_equal("hello\u1234", res.body) + assert_equal(Encoding::UTF_8, res.body.encoding) + end +end + +class TestNetHTTPPartialResponse < Test::Unit::TestCase + CONFIG = { + 'host' => '127.0.0.1', + 'proxy_host' => nil, + 'proxy_port' => nil, + } + + include TestNetHTTPUtils + + def test_partial_response + str = "0123456789" + @server.mount_proc('/') do |req, res| + res.status = 200 + res['Content-Type'] = 'text/plain' + + res.body = str + res['Content-Length'] = str.length + 1 + end + @server.mount_proc('/show_ip') { |req, res| res.body = req.remote_ip } + + http = Net::HTTP.new(config('host'), config('port')) + res = http.get('/') + assert_equal(str, res.body) + + http = Net::HTTP.new(config('host'), config('port')) + http.ignore_eof = false + assert_raise(EOFError) {http.get('/')} + end +end + +class TestNetHTTPInRactor < Test::Unit::TestCase + CONFIG = { + 'host' => '127.0.0.1', + 'proxy_host' => nil, + 'proxy_port' => nil, + } + + include TestNetHTTPUtils + + def test_get + assert_ractor(<<~RUBY, require: 'net/http') + expected = #{$test_net_http_data.dump}.b + ret = Ractor.new { + host = #{config('host').dump} + port = #{config('port')} + Net::HTTP.start(host, port) { |http| + res = http.get('/') + res.body + } + }.value + assert_equal expected, ret + RUBY + end +end if defined?(Ractor) && Ractor.method_defined?(:value) diff --git a/test/net/http/test_http_request.rb b/test/net/http/test_http_request.rb index b7515b7e98..9f5cf4f8f5 100644 --- a/test/net/http/test_http_request.rb +++ b/test/net/http/test_http_request.rb @@ -1,7 +1,6 @@ # frozen_string_literal: false require 'net/http' require 'test/unit' -require 'stringio' class HTTPRequestTest < Test::Unit::TestCase @@ -47,8 +46,9 @@ class HTTPRequestTest < Test::Unit::TestCase assert_not_predicate req, :response_body_permitted? expected = { - 'accept' => %w[*/*], - 'user-agent' => %w[Ruby], + 'accept' => %w[*/*], + "accept-encoding" => %w[gzip;q=1.0,deflate;q=0.6,identity;q=0.3], + 'user-agent' => %w[Ruby], } assert_equal expected, req.to_hash @@ -74,7 +74,20 @@ class HTTPRequestTest < Test::Unit::TestCase assert_equal "/foo", req.path assert_equal "example.com", req['Host'] + req = Net::HTTP::Get.new(URI("https://203.0.113.1/foo")) + assert_equal "/foo", req.path + assert_equal "203.0.113.1", req['Host'] + + req = Net::HTTP::Get.new(URI("https://203.0.113.1:8000/foo")) + assert_equal "/foo", req.path + assert_equal "203.0.113.1:8000", req['Host'] + + req = Net::HTTP::Get.new(URI("https://[2001:db8::1]:8000/foo")) + assert_equal "/foo", req.path + assert_equal "[2001:db8::1]:8000", req['Host'] + assert_raise(ArgumentError){ Net::HTTP::Get.new(URI("urn:ietf:rfc:7231")) } + assert_raise(ArgumentError){ Net::HTTP::Get.new(URI("http://")) } end def test_header_set @@ -88,5 +101,25 @@ class HTTPRequestTest < Test::Unit::TestCase 'Bug #7831 - do not decode content if the user overrides' end if Net::HTTP::HAVE_ZLIB + def test_update_uri + req = Net::HTTP::Get.new(URI.parse("http://203.0.113.1")) + req.update_uri("test", 8080, false) + assert_equal "203.0.113.1", req.uri.host + assert_equal 8080, req.uri.port + + req = Net::HTTP::Get.new(URI.parse("http://203.0.113.1:2020")) + req.update_uri("test", 8080, false) + assert_equal "203.0.113.1", req.uri.host + assert_equal 8080, req.uri.port + + req = Net::HTTP::Get.new(URI.parse("http://[2001:db8::1]")) + req.update_uri("test", 8080, false) + assert_equal "[2001:db8::1]", req.uri.host + assert_equal 8080, req.uri.port + + req = Net::HTTP::Get.new(URI.parse("http://[2001:db8::1]:2020")) + req.update_uri("test", 8080, false) + assert_equal "[2001:db8::1]", req.uri.host + assert_equal 8080, req.uri.port + end end - diff --git a/test/net/http/test_httpheader.rb b/test/net/http/test_httpheader.rb index cb4678a805..69563168db 100644 --- a/test/net/http/test_httpheader.rb +++ b/test/net/http/test_httpheader.rb @@ -28,7 +28,11 @@ class HTTPHeaderTest < Test::Unit::TestCase assert_raise(NoMethodError){ @c.initialize_http_header("foo"=>[]) } assert_raise(ArgumentError){ @c.initialize_http_header("foo"=>"a\nb") } assert_raise(ArgumentError){ @c.initialize_http_header("foo"=>"a\rb") } - assert_raise(ArgumentError){ @c.initialize_http_header("foo"=>"a\xff") } + end + + def test_initialize_with_broken_coderange + error = RUBY_VERSION >= "3.2" ? Encoding::CompatibilityError : ArgumentError + assert_raise(error){ @c.initialize_http_header("foo"=>"a\xff") } end def test_initialize_with_symbol @@ -119,7 +123,19 @@ class HTTPHeaderTest < Test::Unit::TestCase class D; include Net::HTTPHeader; end def test_nil_variable_header - assert_nothing_raised { D.new.initialize_http_header({Authorization: nil}) } + assert_nothing_raised do + assert_warning("#{__FILE__}:#{__LINE__+1}: warning: net/http: nil HTTP header: Authorization\n") do + D.new.initialize_http_header({Authorization: nil}) + end + end + end + + def test_duplicated_variable_header + assert_nothing_raised do + assert_warning("#{__FILE__}:#{__LINE__+1}: warning: net/http: duplicated HTTP header: Authorization\n") do + D.new.initialize_http_header({"AUTHORIZATION": "yes", "Authorization": "no"}) + end + end end def test_delete @@ -296,6 +312,18 @@ class HTTPHeaderTest < Test::Unit::TestCase end def test_content_range + @c['Content-Range'] = "bytes 0-499/1000" + assert_equal 0..499, @c.content_range + @c['Content-Range'] = "bytes 1-500/1000" + assert_equal 1..500, @c.content_range + @c['Content-Range'] = "bytes 1-1/1000" + assert_equal 1..1, @c.content_range + @c['Content-Range'] = "tokens 1-1/1000" + assert_equal nil, @c.content_range + + try_invalid_content_range "invalid" + try_invalid_content_range "bytes 123-abc" + try_invalid_content_range "bytes abc-123" end def test_range_length @@ -305,6 +333,15 @@ class HTTPHeaderTest < Test::Unit::TestCase assert_equal 500, @c.range_length @c['Content-Range'] = "bytes 1-1/1000" assert_equal 1, @c.range_length + @c['Content-Range'] = "tokens 1-1/1000" + assert_equal nil, @c.range_length + + try_invalid_content_range "bytes 1-1/abc" + end + + def try_invalid_content_range(s) + @c['Content-Range'] = "#{s}" + assert_raise(Net::HTTPHeaderSyntaxError, s){ @c.content_range } end def test_chunked? diff --git a/test/net/http/test_httpresponse.rb b/test/net/http/test_httpresponse.rb index a03bb2e152..01281063cd 100644 --- a/test/net/http/test_httpresponse.rb +++ b/test/net/http/test_httpresponse.rb @@ -54,6 +54,241 @@ EOS assert_equal 'hello', body end + def test_read_body_body_encoding_false + body = "hello\u1234" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{body.bytesize} + +#{body} +EOS + + res = Net::HTTPResponse.read_new(io) + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal "hello\u1234".b, body + assert_equal Encoding::ASCII_8BIT, body.encoding + end + + def test_read_body_body_encoding_encoding + body = "hello\u1234" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{body.bytesize} + +#{body} +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = Encoding.find('utf-8') + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal "hello\u1234", body + assert_equal Encoding::UTF_8, body.encoding + end + + def test_read_body_body_encoding_string + body = "hello\u1234" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{body.bytesize} + +#{body} +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = 'utf-8' + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal "hello\u1234", body + assert_equal Encoding::UTF_8, body.encoding + end + + def test_read_body_body_encoding_true_without_content_type_header + body = "hello\u1234" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{body.bytesize} + +#{body} +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = true + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal "hello\u1234".b, body + assert_equal Encoding::ASCII_8BIT, body.encoding + end + + def test_read_body_body_encoding_true_with_utf8_content_type_header + body = "hello\u1234" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{body.bytesize} +Content-Type: text/plain; charset=utf-8 + +#{body} +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = true + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal "hello\u1234", body + assert_equal Encoding::UTF_8, body.encoding + end + + def test_read_body_body_encoding_true_with_iso_8859_1_content_type_header + body = "hello\u1234" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{body.bytesize} +Content-Type: text/plain; charset=iso-8859-1 + +#{body} +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = true + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal "hello\u1234".force_encoding("ISO-8859-1"), body + assert_equal Encoding::ISO_8859_1, body.encoding + end + + def test_read_body_body_encoding_true_with_utf8_meta_charset + res_body = "<html><meta charset=\"utf-8\">hello\u1234</html>" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{res_body.bytesize} +Content-Type: text/html + +#{res_body} +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = true + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal res_body, body + assert_equal Encoding::UTF_8, body.encoding + end + + def test_read_body_body_encoding_true_with_iso8859_1_meta_charset + res_body = "<html><meta charset=\"iso-8859-1\">hello\u1234</html>" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{res_body.bytesize} +Content-Type: text/html + +#{res_body} +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = true + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal res_body.force_encoding("ISO-8859-1"), body + assert_equal Encoding::ISO_8859_1, body.encoding + end + + def test_read_body_body_encoding_true_with_utf8_meta_content_charset + res_body = "<meta http-equiv='content-type' content='text/html; charset=UTF-8'>hello\u1234</html>" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{res_body.bytesize} +Content-Type: text/html + +#{res_body} +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = true + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal res_body, body + assert_equal Encoding::UTF_8, body.encoding + end + + def test_read_body_body_encoding_true_with_iso8859_1_meta_content_charset + res_body = "<meta http-equiv='content-type' content='text/html; charset=ISO-8859-1'>hello\u1234</html>" + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: #{res_body.bytesize} +Content-Type: text/html + +#{res_body} +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = true + + body = nil + + res.reading_body io, true do + body = res.read_body + end + + assert_equal res_body.force_encoding("ISO-8859-1"), body + assert_equal Encoding::ISO_8859_1, body.encoding + end + def test_read_body_block io = dummy_io(<<EOS) HTTP/1.1 200 OK @@ -77,6 +312,10 @@ EOS end def test_read_body_block_mod + # http://ci.rvm.jp/results/trunk-rjit-wait@silicon-docker/3019353 + if defined?(RubyVM::RJIT) && RubyVM::RJIT.enabled? + omit 'too unstable with --jit-wait, and extending read_timeout did not help it' + end IO.pipe do |r, w| buf = 'x' * 1024 buf.freeze @@ -123,9 +362,11 @@ EOS if Net::HTTP::HAVE_ZLIB assert_equal nil, res['content-encoding'] + assert_equal '5', res['content-length'] assert_equal 'hello', body else assert_equal 'deflate', res['content-encoding'] + assert_equal '13', res['content-length'] assert_equal "x\x9C\xCBH\xCD\xC9\xC9\a\x00\x06,\x02\x15", body end end @@ -151,9 +392,11 @@ EOS if Net::HTTP::HAVE_ZLIB assert_equal nil, res['content-encoding'] + assert_equal '5', res['content-length'] assert_equal 'hello', body else assert_equal 'DEFLATE', res['content-encoding'] + assert_equal '13', res['content-length'] assert_equal "x\x9C\xCBH\xCD\xC9\xC9\a\x00\x06,\x02\x15", body end end @@ -184,9 +427,11 @@ EOS if Net::HTTP::HAVE_ZLIB assert_equal nil, res['content-encoding'] + assert_equal nil, res['content-length'] assert_equal 'hello', body else assert_equal 'deflate', res['content-encoding'] + assert_equal nil, res['content-length'] assert_equal "x\x9C\xCBH\xCD\xC9\xC9\a\x00\x06,\x02\x15", body end end @@ -211,6 +456,7 @@ EOS end assert_equal 'deflate', res['content-encoding'], 'Bug #7831' + assert_equal '13', res['content-length'] assert_equal "x\x9C\xCBH\xCD\xC9\xC9\a\x00\x06,\x02\x15", body, 'Bug #7381' end @@ -234,9 +480,11 @@ EOS if Net::HTTP::HAVE_ZLIB assert_equal nil, res['content-encoding'] + assert_equal nil, res['content-length'] assert_equal 'hello', body else assert_equal 'deflate', res['content-encoding'] + assert_equal nil, res['content-length'] assert_equal "x\x9C\xCBH\xCD\xC9\xC9\a\x00\x06,\x02\x15\r\n", body end end @@ -284,9 +532,11 @@ EOS if Net::HTTP::HAVE_ZLIB assert_equal nil, res['content-encoding'] + assert_equal '0', res['content-length'] assert_equal '', body else assert_equal 'deflate', res['content-encoding'] + assert_equal '0', res['content-length'] assert_equal '', body end end @@ -310,9 +560,11 @@ EOS if Net::HTTP::HAVE_ZLIB assert_equal nil, res['content-encoding'] + assert_equal nil, res['content-length'] assert_equal '', body else assert_equal 'deflate', res['content-encoding'] + assert_equal nil, res['content-length'] assert_equal '', body end end @@ -337,6 +589,41 @@ EOS assert_equal 'hello', body end + def test_read_body_receiving_no_body + io = dummy_io(<<EOS) +HTTP/1.1 204 OK +Connection: close + +EOS + + res = Net::HTTPResponse.read_new(io) + res.body_encoding = 'utf-8' + + body = 'something to override' + + res.reading_body io, true do + body = res.read_body + end + + assert_equal nil, body + assert_equal nil, res.body + end + + def test_read_body_outside_of_reading_body + io = dummy_io(<<EOS) +HTTP/1.1 200 OK +Connection: close +Content-Length: 0 + +EOS + + res = Net::HTTPResponse.read_new(io) + + assert_raise IOError do + res.read_body + end + end + def test_uri_equals uri = URI 'http://example' diff --git a/test/net/http/test_https.rb b/test/net/http/test_https.rb index c1d486470a..f5b21b901f 100644 --- a/test/net/http/test_https.rb +++ b/test/net/http/test_https.rb @@ -1,113 +1,156 @@ # frozen_string_literal: false require "test/unit" +require_relative "utils" begin require 'net/https' - require 'stringio' - require 'timeout' - require File.expand_path("utils", File.dirname(__FILE__)) rescue LoadError # should skip this test end +return unless defined?(OpenSSL::SSL) + class TestNetHTTPS < Test::Unit::TestCase include TestNetHTTPUtils - def self.fixture(key) + def self.read_fixture(key) File.read(File.expand_path("../fixtures/#{key}", __dir__)) end - CA_CERT = OpenSSL::X509::Certificate.new(fixture("cacert.pem")) - SERVER_KEY = OpenSSL::PKey.read(fixture("server.key")) - SERVER_CERT = OpenSSL::X509::Certificate.new(fixture("server.crt")) - DHPARAMS = OpenSSL::PKey::DH.new(fixture("dhparams.pem")) + HOST = 'localhost' + HOST_IP = '127.0.0.1' + CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem")) + SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key")) + SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt")) TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) } CONFIG = { - 'host' => '127.0.0.1', + 'host' => HOST, 'proxy_host' => nil, 'proxy_port' => nil, 'ssl_enable' => true, 'ssl_certificate' => SERVER_CERT, 'ssl_private_key' => SERVER_KEY, - 'ssl_tmp_dh_callback' => proc { DHPARAMS }, } def test_get - http = Net::HTTP.new("localhost", config("port")) + http = Net::HTTP.new(HOST, config("port")) + http.use_ssl = true + http.cert_store = TEST_STORE + http.request_get("/") {|res| + assert_equal($test_net_http_data, res.body) + assert_equal(SERVER_CERT.to_der, http.peer_cert.to_der) + } + end + + def test_get_SNI + http = Net::HTTP.new(HOST, config("port")) + http.ipaddr = config('host') http.use_ssl = true http.cert_store = TEST_STORE - certs = [] - http.verify_callback = Proc.new do |preverify_ok, store_ctx| - certs << store_ctx.current_cert - preverify_ok - end http.request_get("/") {|res| assert_equal($test_net_http_data, res.body) + assert_equal(SERVER_CERT.to_der, http.peer_cert.to_der) + } + end + + def test_get_SNI_proxy + TCPServer.open(HOST_IP, 0) {|serv| + _, port, _, _ = serv.addr + client_thread = Thread.new { + proxy = Net::HTTP.Proxy(HOST_IP, port, 'user', 'password') + http = proxy.new("foo.example.org", 8000) + http.ipaddr = "192.0.2.1" + http.use_ssl = true + http.cert_store = TEST_STORE + begin + http.start + rescue EOFError + end + } + server_thread = Thread.new { + sock = serv.accept + begin + proxy_request = sock.gets("\r\n\r\n") + assert_equal( + "CONNECT 192.0.2.1:8000 HTTP/1.1\r\n" + + "Host: foo.example.org:8000\r\n" + + "Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==\r\n" + + "\r\n", + proxy_request, + "[ruby-dev:25673]") + ensure + sock.close + end + } + assert_join_threads([client_thread, server_thread]) } - assert_equal(CA_CERT.to_der, certs[0].to_der) - assert_equal(SERVER_CERT.to_der, certs[1].to_der) - rescue SystemCallError - skip $! + + end + + def test_get_SNI_failure + TestNetHTTPUtils.clean_http_proxy_env do + http = Net::HTTP.new("invalidservername", config("port")) + http.ipaddr = config('host') + http.use_ssl = true + http.cert_store = TEST_STORE + @log_tester = lambda {|_| } + assert_raise(OpenSSL::SSL::SSLError){ http.start } + end end def test_post - http = Net::HTTP.new("localhost", config("port")) + http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.cert_store = TEST_STORE data = config('ssl_private_key').to_der http.request_post("/", data, {'content-type' => 'application/x-www-form-urlencoded'}) {|res| assert_equal(data, res.body) } - rescue SystemCallError - skip $! end def test_session_reuse - # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. - # See https://github.com/openssl/openssl/pull/5967 for details. - skip if OpenSSL::OPENSSL_LIBRARY_VERSION =~ /OpenSSL 1.1.0h/ - - http = Net::HTTP.new("localhost", config("port")) + http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.cert_store = TEST_STORE + if OpenSSL::OPENSSL_LIBRARY_VERSION =~ /LibreSSL (\d+\.\d+)/ && $1.to_f > 3.19 + # LibreSSL 3.2 defaults to TLSv1.3 in server and client, which doesn't currently + # support session resuse. Limiting the version to the TLSv1.2 stack allows + # this test to continue to work on LibreSSL 3.2+. LibreSSL may eventually + # support session reuse, but there are no current plans to do so. + http.ssl_version = :TLSv1_2 + end + http.start + session_reused = http.instance_variable_get(:@socket).io.session_reused? + assert_false session_reused unless session_reused.nil? # can not detect re-use under JRuby http.get("/") http.finish http.start - http.get("/") - - socket = http.instance_variable_get(:@socket).io - assert_equal true, socket.session_reused? - + session_reused = http.instance_variable_get(:@socket).io.session_reused? + assert_true session_reused unless session_reused.nil? # can not detect re-use under JRuby + assert_equal $test_net_http_data, http.get("/").body http.finish - rescue SystemCallError - skip $! end def test_session_reuse_but_expire - # FIXME: The new_session_cb is known broken for clients in OpenSSL 1.1.0h. - skip if OpenSSL::OPENSSL_LIBRARY_VERSION =~ /OpenSSL 1.1.0h/ - - http = Net::HTTP.new("localhost", config("port")) + http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.cert_store = TEST_STORE - http.ssl_timeout = -1 + http.ssl_timeout = 1 http.start http.get("/") http.finish - + sleep 1.25 http.start http.get("/") socket = http.instance_variable_get(:@socket).io - assert_equal false, socket.session_reused? + assert_equal false, socket.session_reused?, "NOTE: OpenSSL library version is #{OpenSSL::OPENSSL_LIBRARY_VERSION}" http.finish - rescue SystemCallError - skip $! end if ENV["RUBY_OPENSSL_TEST_ALL"] @@ -122,58 +165,71 @@ class TestNetHTTPS < Test::Unit::TestCase end def test_verify_none - http = Net::HTTP.new("localhost", config("port")) + http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.verify_mode = OpenSSL::SSL::VERIFY_NONE http.request_get("/") {|res| assert_equal($test_net_http_data, res.body) } - rescue SystemCallError - skip $! + end + + def test_skip_hostname_verification + TestNetHTTPUtils.clean_http_proxy_env do + http = Net::HTTP.new('invalidservername', config('port')) + http.ipaddr = config('host') + http.use_ssl = true + http.cert_store = TEST_STORE + http.verify_hostname = false + assert_nothing_raised { http.start } + ensure + http.finish if http&.started? + end + end + + def test_fail_if_verify_hostname_is_true + TestNetHTTPUtils.clean_http_proxy_env do + http = Net::HTTP.new('invalidservername', config('port')) + http.ipaddr = config('host') + http.use_ssl = true + http.cert_store = TEST_STORE + http.verify_hostname = true + @log_tester = lambda { |_| } + assert_raise(OpenSSL::SSL::SSLError) { http.start } + end end def test_certificate_verify_failure - http = Net::HTTP.new("localhost", config("port")) + http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true ex = assert_raise(OpenSSL::SSL::SSLError){ - begin - http.request_get("/") {|res| } - rescue SystemCallError - skip $! - end + http.request_get("/") {|res| } } assert_match(/certificate verify failed/, ex.message) - unless /mswin|mingw/ =~ RUBY_PLATFORM - # on Windows, Errno::ECONNRESET will be raised, and it'll be eaten by - # WEBrick - @log_tester = lambda {|log| - assert_equal(1, log.length) - assert_match(/ERROR OpenSSL::SSL::SSLError:/, log[0]) - } - end end - def test_identity_verify_failure - # the certificate's subject has CN=localhost - http = Net::HTTP.new("127.0.0.1", config("port")) + def test_verify_callback + http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.cert_store = TEST_STORE - @log_tester = lambda {|_| } - ex = assert_raise(OpenSSL::SSL::SSLError){ - http.request_get("/") {|res| } + certs = [] + http.verify_callback = Proc.new {|preverify_ok, store_ctx| + certs << store_ctx.current_cert + preverify_ok } - re_msg = /certificate verify failed|hostname \"127.0.0.1\" does not match/ - assert_match(re_msg, ex.message) + http.request_get("/") {|res| + assert_equal($test_net_http_data, res.body) + } + assert_equal(SERVER_CERT.to_der, certs.last.to_der) end def test_timeout_during_SSL_handshake bug4246 = "expected the SSL connection to have timed out but have not. [ruby-core:34203]" # listen for connections... but deliberately do not complete SSL handshake - TCPServer.open('localhost', 0) {|server| + TCPServer.open(HOST, 0) {|server| port = server.addr[1] - conn = Net::HTTP.new('localhost', port) + conn = Net::HTTP.new(HOST, port) conn.use_ssl = true conn.read_timeout = 0.01 conn.open_timeout = 0.01 @@ -188,7 +244,7 @@ class TestNetHTTPS < Test::Unit::TestCase end def test_min_version - http = Net::HTTP.new("localhost", config("port")) + http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.min_version = :TLS1 http.cert_store = TEST_STORE @@ -198,18 +254,72 @@ class TestNetHTTPS < Test::Unit::TestCase end def test_max_version - http = Net::HTTP.new("127.0.0.1", config("port")) + http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true http.max_version = :SSL2 - http.verify_callback = Proc.new do |preverify_ok, store_ctx| - true - end + http.cert_store = TEST_STORE @log_tester = lambda {|_| } ex = assert_raise(OpenSSL::SSL::SSLError){ http.request_get("/") {|res| } } - re_msg = /\ASSL_connect returned=1 errno=0 |SSL_CTX_set_max_proto_version/ + re_msg = /\ASSL_connect returned=1 errno=0 |SSL_CTX_set_max_proto_version|No appropriate protocol/ assert_match(re_msg, ex.message) end -end if defined?(OpenSSL::SSL) + def test_ractor + assert_ractor(<<~RUBY, require: 'net/https') + expected = #{$test_net_http_data.dump}.b + ret = Ractor.new { + host = #{HOST.dump} + port = #{config('port')} + ca_cert_pem = #{CA_CERT.to_pem.dump} + cert_store = OpenSSL::X509::Store.new.tap { |s| + s.add_cert(OpenSSL::X509::Certificate.new(ca_cert_pem)) + } + Net::HTTP.start(host, port, use_ssl: true, cert_store: cert_store) { |http| + res = http.get('/') + res.body + } + }.value + assert_equal expected, ret + RUBY + end if defined?(Ractor) && Ractor.method_defined?(:value) +end + +class TestNetHTTPSIdentityVerifyFailure < Test::Unit::TestCase + include TestNetHTTPUtils + + def self.read_fixture(key) + File.read(File.expand_path("../fixtures/#{key}", __dir__)) + end + + HOST = 'localhost' + HOST_IP = '127.0.0.1' + CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem")) + SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key")) + SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt")) + TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) } + + CONFIG = { + 'host' => HOST_IP, + 'proxy_host' => nil, + 'proxy_port' => nil, + 'ssl_enable' => true, + 'ssl_certificate' => SERVER_CERT, + 'ssl_private_key' => SERVER_KEY, + } + + def test_identity_verify_failure + # the certificate's subject has CN=localhost + http = Net::HTTP.new(HOST_IP, config("port")) + http.use_ssl = true + http.cert_store = TEST_STORE + @log_tester = lambda {|_| } + ex = assert_raise(OpenSSL::SSL::SSLError){ + http.request_get("/") {|res| } + sleep 0.5 + } + re_msg = /certificate verify failed|hostname \"#{HOST_IP}\" does not match/ + assert_match(re_msg, ex.message) + end +end diff --git a/test/net/http/test_https_proxy.rb b/test/net/http/test_https_proxy.rb index f833f1a1e3..237c16e64d 100644 --- a/test/net/http/test_https_proxy.rb +++ b/test/net/http/test_https_proxy.rb @@ -5,14 +5,10 @@ rescue LoadError end require 'test/unit' +return unless defined?(OpenSSL::SSL) + class HTTPSProxyTest < Test::Unit::TestCase def test_https_proxy_authentication - begin - OpenSSL - rescue LoadError - skip 'autoload problem. see [ruby-dev:45021][Bug #5786]' - end - TCPServer.open("127.0.0.1", 0) {|serv| _, port, _, _ = serv.addr client_thread = Thread.new { @@ -43,5 +39,46 @@ class HTTPSProxyTest < Test::Unit::TestCase assert_join_threads([client_thread, server_thread]) } end -end if defined?(OpenSSL) + + def read_fixture(key) + File.read(File.expand_path("../fixtures/#{key}", __dir__)) + end + + def test_https_proxy_ssl_connection + TCPServer.open("127.0.0.1", 0) {|tcpserver| + ctx = OpenSSL::SSL::SSLContext.new + ctx.key = OpenSSL::PKey.read(read_fixture("server.key")) + ctx.cert = OpenSSL::X509::Certificate.new(read_fixture("server.crt")) + serv = OpenSSL::SSL::SSLServer.new(tcpserver, ctx) + + _, port, _, _ = serv.addr + client_thread = Thread.new { + proxy = Net::HTTP.Proxy("127.0.0.1", port, 'user', 'password', true) + http = proxy.new("foo.example.org", 8000) + http.use_ssl = true + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + begin + http.start + rescue EOFError + end + } + server_thread = Thread.new { + sock = serv.accept + begin + proxy_request = sock.gets("\r\n\r\n") + assert_equal( + "CONNECT foo.example.org:8000 HTTP/1.1\r\n" + + "Host: foo.example.org:8000\r\n" + + "Proxy-Authorization: Basic dXNlcjpwYXNzd29yZA==\r\n" + + "\r\n", + proxy_request, + "[ruby-core:96672]") + ensure + sock.close + end + } + assert_join_threads([client_thread, server_thread]) + } + end +end diff --git a/test/net/http/utils.rb b/test/net/http/utils.rb index dbfd112f31..0b9e440e7c 100644 --- a/test/net/http/utils.rb +++ b/test/net/http/utils.rb @@ -1,13 +1,234 @@ # frozen_string_literal: false -require 'webrick' -begin - require "webrick/https" -rescue LoadError - # SSL features cannot be tested -end -require 'webrick/httpservlet/abstract' +require 'socket' module TestNetHTTPUtils + + class Forbidden < StandardError; end + + class HTTPServer + def initialize(config, &block) + @config = config + @server = TCPServer.new(@config['host'], 0) + @port = @server.addr[1] + @procs = {} + + if @config['ssl_enable'] + require 'openssl' + context = OpenSSL::SSL::SSLContext.new + context.cert = @config['ssl_certificate'] + context.key = @config['ssl_private_key'] + @ssl_server = OpenSSL::SSL::SSLServer.new(@server, context) + end + + @block = block + end + + def start + @thread = Thread.new do + loop do + socket = (@ssl_server || @server).accept + run(socket) + rescue + ensure + socket&.close + end + ensure + (@ssl_server || @server).close + end + end + + def run(socket) + handle_request(socket) + end + + def shutdown + @thread&.kill + @thread&.join + end + + def mount(path, proc) + @procs[path] = proc + end + + def mount_proc(path, &block) + mount(path, block.to_proc) + end + + def handle_request(socket) + request_line = socket.gets + return if request_line.nil? || request_line.strip.empty? + + method, path, _version = request_line.split + headers = {} + while (line = socket.gets) + break if line.strip.empty? + key, value = line.split(': ', 2) + headers[key] = value.strip + end + + if headers['Expect'] == '100-continue' + socket.write "HTTP/1.1 100 Continue\r\n\r\n" + end + + # Set default Content-Type if not provided + if !headers['Content-Type'] && (method == 'POST' || method == 'PUT' || method == 'PATCH') + headers['Content-Type'] = 'application/octet-stream' + end + + req = Request.new(method, path, headers, socket) + if @procs.key?(req.path) || @procs.key?("#{req.path}/") + proc = @procs[req.path] || @procs["#{req.path}/"] + res = Response.new(socket) + begin + proc.call(req, res) + rescue Forbidden + res.status = 403 + end + res.finish + else + @block.call(method, path, headers, socket) + end + end + + def port + @port + end + + class Request + attr_reader :method, :path, :headers, :query, :body + def initialize(method, path, headers, socket) + @method = method + @path, @query = parse_path_and_query(path) + @headers = headers + @socket = socket + if method == 'POST' && (@path == '/continue' || @headers['Content-Type'].include?('multipart/form-data')) + if @headers['Transfer-Encoding'] == 'chunked' + @body = read_chunked_body + else + @body = read_body + end + @query = @body.split('&').each_with_object({}) do |pair, hash| + key, value = pair.split('=') + hash[key] = value + end if @body && @body.include?('=') + end + end + + def [](key) + @headers[key.downcase] + end + + def []=(key, value) + @headers[key.downcase] = value + end + + def continue + @socket.write "HTTP\/1.1 100 continue\r\n\r\n" + end + + def remote_ip + @socket.peeraddr[3] + end + + def peeraddr + @socket.peeraddr + end + + private + + def parse_path_and_query(path) + path, query_string = path.split('?', 2) + query = {} + if query_string + query_string.split('&').each do |pair| + key, value = pair.split('=', 2) + query[key] = value + end + end + [path, query] + end + + def read_body + content_length = @headers['Content-Length']&.to_i + return unless content_length && content_length > 0 + @socket.read(content_length) + end + + def read_chunked_body + body = "" + while (chunk_size = @socket.gets.strip.to_i(16)) > 0 + body << @socket.read(chunk_size) + @socket.read(2) # read \r\n after each chunk + end + body + end + end + + class Response + attr_accessor :body, :headers, :status, :chunked, :cookies + def initialize(client) + @client = client + @body = "" + @headers = {} + @status = 200 + @chunked = false + @cookies = [] + end + + def [](key) + @headers[key.downcase] + end + + def []=(key, value) + @headers[key.downcase] = value + end + + def write_chunk(chunk) + return unless @chunked + @client.write("#{chunk.bytesize.to_s(16)}\r\n") + @client.write("#{chunk}\r\n") + end + + def finish + @client.write build_response_headers + if @chunked + write_chunk(@body) + @client.write "0\r\n\r\n" + else + @client.write @body + end + end + + private + + def build_response_headers + response = "HTTP/1.1 #{@status} #{status_message(@status)}\r\n" + if @chunked + @headers['Transfer-Encoding'] = 'chunked' + else + @headers['Content-Length'] = @body.bytesize.to_s + end + @headers.each do |key, value| + response << "#{key}: #{value}\r\n" + end + @cookies.each do |cookie| + response << "Set-Cookie: #{cookie}\r\n" + end + response << "\r\n" + response + end + + def status_message(code) + case code + when 200 then 'OK' + when 301 then 'Moved Permanently' + when 403 then 'Forbidden' + else 'Unknown' + end + end + end + end + def start(&block) new().start(&block) end @@ -15,7 +236,7 @@ module TestNetHTTPUtils def new klass = Net::HTTP::Proxy(config('proxy_host'), config('proxy_port')) http = klass.new(config('host'), config('port')) - http.set_debug_output logfile() + http.set_debug_output logfile http end @@ -25,7 +246,7 @@ module TestNetHTTPUtils end def logfile - $DEBUG ? $stderr : NullWriter.new + $stderr if $DEBUG end def setup @@ -33,78 +254,106 @@ module TestNetHTTPUtils end def teardown + sleep 0.5 if @config['ssl_enable'] if @server @server.shutdown - @server_thread.join - WEBrick::Utils::TimeoutHandler.terminate end @log_tester.call(@log) if @log_tester - # resume global state Net::HTTP.version_1_2 end def spawn_server @log = [] - @log_tester = lambda {|log| assert_equal([], log ) } + @log_tester = lambda {|log| assert_equal([], log) } @config = self.class::CONFIG - server_config = { - :BindAddress => config('host'), - :Port => 0, - :Logger => WEBrick::Log.new(@log, WEBrick::BasicLog::WARN), - :AccessLog => [], - :ServerType => Thread, - } - server_config[:OutputBufferSize] = 4 if config('chunked') - server_config[:RequestTimeout] = config('RequestTimeout') if config('RequestTimeout') - if defined?(OpenSSL) and config('ssl_enable') - server_config.update({ - :SSLEnable => true, - :SSLCertificate => config('ssl_certificate'), - :SSLPrivateKey => config('ssl_private_key'), - :SSLTmpDhCallback => config('ssl_tmp_dh_callback'), - }) - end - @server = WEBrick::HTTPServer.new(server_config) - @server.mount('/', Servlet, config('chunked')) - @server_thread = @server.start - @config['port'] = @server[:Port] + @server = HTTPServer.new(@config) do |method, path, headers, socket| + @log << "DEBUG accept: #{@config['host']}:#{socket.addr[1]}" if @logger_level == :debug + case method + when 'HEAD' + handle_head(path, headers, socket) + when 'GET' + handle_get(path, headers, socket) + when 'POST' + handle_post(path, headers, socket) + when 'PATCH' + handle_patch(path, headers, socket) + else + socket.print "HTTP/1.1 405 Method Not Allowed\r\nContent-Length: 0\r\n\r\n" + end + end + @server.start + @config['port'] = @server.port + end + + def handle_head(path, headers, socket) + if headers['Accept'] != '*/*' + content_type = headers['Accept'] + else + content_type = $test_net_http_data_type + end + response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{$test_net_http_data.bytesize}" + socket.print(response) + end + + def handle_get(path, headers, socket) + if headers['Accept'] != '*/*' + content_type = headers['Accept'] + else + content_type = $test_net_http_data_type + end + response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{$test_net_http_data.bytesize}\r\n\r\n#{$test_net_http_data}" + socket.print(response) + end + + def handle_post(path, headers, socket) + body = socket.read(headers['Content-Length'].to_i) + scheme = headers['X-Request-Scheme'] || 'http' + host = @config['host'] + port = socket.addr[1] + content_type = headers['Content-Type'] || 'application/octet-stream' + charset = parse_content_type(content_type)[1] + path = "#{scheme}://#{host}:#{port}#{path}" + path = path.encode(charset) if charset + response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{body.bytesize}\r\nX-request-uri: #{path}\r\n\r\n#{body}" + socket.print(response) + end + + def handle_patch(path, headers, socket) + body = socket.read(headers['Content-Length'].to_i) + content_type = headers['Content-Type'] || 'application/octet-stream' + response = "HTTP/1.1 200 OK\r\nContent-Type: #{content_type}\r\nContent-Length: #{body.bytesize}\r\n\r\n#{body}" + socket.print(response) + end + + def parse_content_type(content_type) + return [nil, nil] unless content_type + type, *params = content_type.split(';').map(&:strip) + charset = params.find { |param| param.start_with?('charset=') } + charset = charset.split('=', 2).last if charset + [type, charset] end $test_net_http = nil - $test_net_http_data = (0...256).to_a.map {|i| i.chr }.join('') * 64 + $test_net_http_data = (0...256).to_a.map { |i| i.chr }.join('') * 64 $test_net_http_data.force_encoding("ASCII-8BIT") $test_net_http_data_type = 'application/octet-stream' - class Servlet < WEBrick::HTTPServlet::AbstractServlet - def initialize(this, chunked = false) - @chunked = chunked - end - - def do_GET(req, res) - res['Content-Type'] = $test_net_http_data_type - res.body = $test_net_http_data - res.chunked = @chunked - end + def self.clean_http_proxy_env + orig = { + 'http_proxy' => ENV['http_proxy'], + 'http_proxy_user' => ENV['http_proxy_user'], + 'http_proxy_pass' => ENV['http_proxy_pass'], + 'no_proxy' => ENV['no_proxy'], + } - # echo server - def do_POST(req, res) - res['Content-Type'] = req['Content-Type'] - res['X-request-uri'] = req.request_uri.to_s - res.body = req.body - res.chunked = @chunked + orig.each_key do |key| + ENV.delete key end - def do_PATCH(req, res) - res['Content-Type'] = req['Content-Type'] - res.body = req.body - res.chunked = @chunked + yield + ensure + orig.each do |key, value| + ENV[key] = value end end - - class NullWriter - def <<(s) end - def puts(*args) end - def print(*args) end - def printf(*args) end - end end diff --git a/test/net/imap/test_imap.rb b/test/net/imap/test_imap.rb deleted file mode 100644 index b97be4ffb3..0000000000 --- a/test/net/imap/test_imap.rb +++ /dev/null @@ -1,785 +0,0 @@ -# frozen_string_literal: true - -require "net/imap" -require "test/unit" - -class IMAPTest < Test::Unit::TestCase - CA_FILE = File.expand_path("../fixtures/cacert.pem", __dir__) - SERVER_KEY = File.expand_path("../fixtures/server.key", __dir__) - SERVER_CERT = File.expand_path("../fixtures/server.crt", __dir__) - - def setup - @do_not_reverse_lookup = Socket.do_not_reverse_lookup - Socket.do_not_reverse_lookup = true - @threads = [] - end - - def teardown - if !@threads.empty? - assert_join_threads(@threads) - end - ensure - Socket.do_not_reverse_lookup = @do_not_reverse_lookup - end - - def test_encode_utf7 - assert_equal("foo", Net::IMAP.encode_utf7("foo")) - assert_equal("&-", Net::IMAP.encode_utf7("&")) - - utf8 = "\357\274\241\357\274\242\357\274\243".dup.force_encoding("UTF-8") - s = Net::IMAP.encode_utf7(utf8) - assert_equal("&,yH,Iv8j-", s) - s = Net::IMAP.encode_utf7("foo&#{utf8}-bar".encode("EUC-JP")) - assert_equal("foo&-&,yH,Iv8j--bar", s) - - utf8 = "\343\201\202&".dup.force_encoding("UTF-8") - s = Net::IMAP.encode_utf7(utf8) - assert_equal("&MEI-&-", s) - s = Net::IMAP.encode_utf7(utf8.encode("EUC-JP")) - assert_equal("&MEI-&-", s) - end - - def test_decode_utf7 - assert_equal("&", Net::IMAP.decode_utf7("&-")) - assert_equal("&-", Net::IMAP.decode_utf7("&--")) - - s = Net::IMAP.decode_utf7("&,yH,Iv8j-") - utf8 = "\357\274\241\357\274\242\357\274\243".dup.force_encoding("UTF-8") - assert_equal(utf8, s) - end - - def test_format_date - time = Time.mktime(2009, 7, 24) - s = Net::IMAP.format_date(time) - assert_equal("24-Jul-2009", s) - end - - def test_format_datetime - time = Time.mktime(2009, 7, 24, 1, 23, 45) - s = Net::IMAP.format_datetime(time) - assert_match(/\A24-Jul-2009 01:23 [+\-]\d{4}\z/, s) - end - - if defined?(OpenSSL::SSL::SSLError) - def test_imaps_unknown_ca - assert_raise(OpenSSL::SSL::SSLError) do - imaps_test do |port| - begin - Net::IMAP.new("localhost", - :port => port, - :ssl => true) - rescue SystemCallError - skip $! - end - end - end - end - - def test_imaps_with_ca_file - assert_nothing_raised do - imaps_test do |port| - begin - Net::IMAP.new("localhost", - :port => port, - :ssl => { :ca_file => CA_FILE }) - rescue SystemCallError - skip $! - end - end - end - end - - def test_imaps_verify_none - assert_nothing_raised do - imaps_test do |port| - Net::IMAP.new(server_addr, - :port => port, - :ssl => { :verify_mode => OpenSSL::SSL::VERIFY_NONE }) - end - end - end - - def test_imaps_post_connection_check - assert_raise(OpenSSL::SSL::SSLError) do - imaps_test do |port| - # server_addr is different from the hostname in the certificate, - # so the following code should raise a SSLError. - Net::IMAP.new(server_addr, - :port => port, - :ssl => { :ca_file => CA_FILE }) - end - end - end - end - - if defined?(OpenSSL::SSL) - def test_starttls - imap = nil - starttls_test do |port| - imap = Net::IMAP.new("localhost", :port => port) - imap.starttls(:ca_file => CA_FILE) - imap - end - rescue SystemCallError - skip $! - ensure - if imap && !imap.disconnected? - imap.disconnect - end - end - end - - def start_server - th = Thread.new do - yield - end - @threads << th - sleep 0.1 until th.stop? - end - - def test_unexpected_eof - server = create_tcp_server - port = server.addr[1] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - sock.gets -# sock.print("* BYE terminating connection\r\n") -# sock.print("RUBY0001 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - begin - imap = Net::IMAP.new(server_addr, :port => port) - assert_raise(EOFError) do - imap.logout - end - ensure - imap.disconnect if imap - end - end - - def test_idle - server = create_tcp_server - port = server.addr[1] - requests = [] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - requests.push(sock.gets) - sock.print("+ idling\r\n") - sock.print("* 3 EXISTS\r\n") - sock.print("* 2 EXPUNGE\r\n") - requests.push(sock.gets) - sock.print("RUBY0001 OK IDLE terminated\r\n") - sock.gets - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0002 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - - begin - imap = Net::IMAP.new(server_addr, :port => port) - responses = [] - imap.idle do |res| - responses.push(res) - if res.name == "EXPUNGE" - imap.idle_done - end - end - assert_equal(3, responses.length) - assert_instance_of(Net::IMAP::ContinuationRequest, responses[0]) - assert_equal("EXISTS", responses[1].name) - assert_equal(3, responses[1].data) - assert_equal("EXPUNGE", responses[2].name) - assert_equal(2, responses[2].data) - assert_equal(2, requests.length) - assert_equal("RUBY0001 IDLE\r\n", requests[0]) - assert_equal("DONE\r\n", requests[1]) - imap.logout - ensure - imap.disconnect if imap - end - end - - def test_exception_during_idle - server = create_tcp_server - port = server.addr[1] - requests = [] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - requests.push(sock.gets) - sock.print("+ idling\r\n") - sock.print("* 3 EXISTS\r\n") - sock.print("* 2 EXPUNGE\r\n") - requests.push(sock.gets) - sock.print("RUBY0001 OK IDLE terminated\r\n") - sock.gets - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0002 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - begin - imap = Net::IMAP.new(server_addr, :port => port) - begin - th = Thread.current - m = Monitor.new - in_idle = false - exception_raised = false - c = m.new_cond - raiser = Thread.start do - m.synchronize do - until in_idle - c.wait(0.1) - end - end - th.raise(Interrupt) - m.synchronize do - exception_raised = true - c.signal - end - end - @threads << raiser - imap.idle do |res| - m.synchronize do - in_idle = true - c.signal - until exception_raised - c.wait(0.1) - end - end - end - rescue Interrupt - end - assert_equal(2, requests.length) - assert_equal("RUBY0001 IDLE\r\n", requests[0]) - assert_equal("DONE\r\n", requests[1]) - imap.logout - ensure - imap.disconnect if imap - raiser.kill unless in_idle - end - end - - def test_idle_done_not_during_idle - server = create_tcp_server - port = server.addr[1] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - ensure - sock.close - server.close - end - end - begin - imap = Net::IMAP.new(server_addr, :port => port) - assert_raise(Net::IMAP::Error) do - imap.idle_done - end - ensure - imap.disconnect if imap - end - end - - def test_idle_timeout - server = create_tcp_server - port = server.addr[1] - requests = [] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - requests.push(sock.gets) - sock.print("+ idling\r\n") - sock.print("* 3 EXISTS\r\n") - sock.print("* 2 EXPUNGE\r\n") - requests.push(sock.gets) - sock.print("RUBY0001 OK IDLE terminated\r\n") - sock.gets - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0002 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - - begin - imap = Net::IMAP.new(server_addr, :port => port) - responses = [] - Thread.pass - imap.idle(0.2) do |res| - responses.push(res) - end - # There is no guarantee that this thread has received all the responses, - # so check the response length. - if responses.length > 0 - assert_instance_of(Net::IMAP::ContinuationRequest, responses[0]) - if responses.length > 1 - assert_equal("EXISTS", responses[1].name) - assert_equal(3, responses[1].data) - if responses.length > 2 - assert_equal("EXPUNGE", responses[2].name) - assert_equal(2, responses[2].data) - end - end - end - # Also, there is no guarantee that the server thread has stored - # all the requests into the array, so check the length. - if requests.length > 0 - assert_equal("RUBY0001 IDLE\r\n", requests[0]) - if requests.length > 1 - assert_equal("DONE\r\n", requests[1]) - end - end - imap.logout - ensure - imap.disconnect if imap - end - end - - def test_unexpected_bye - server = create_tcp_server - port = server.addr[1] - start_server do - sock = server.accept - begin - sock.print("* OK Gimap ready for requests from 75.101.246.151 33if2752585qyk.26\r\n") - sock.gets - sock.print("* BYE System Error 33if2752585qyk.26\r\n") - ensure - sock.close - server.close - end - end - begin - imap = Net::IMAP.new(server_addr, :port => port) - assert_raise(Net::IMAP::ByeResponseError) do - imap.login("user", "password") - end - end - end - - def test_exception_during_shutdown - server = create_tcp_server - port = server.addr[1] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - sock.gets - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0001 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - begin - imap = Net::IMAP.new(server_addr, :port => port) - imap.instance_eval do - def @sock.shutdown(*args) - super - ensure - raise "error" - end - end - imap.logout - ensure - assert_raise(RuntimeError) do - imap.disconnect - end - end - end - - def test_connection_closed_during_idle - server = create_tcp_server - port = server.addr[1] - requests = [] - sock = nil - threads = [] - started = false - threads << Thread.start do - started = true - begin - sock = server.accept - sock.print("* OK test server\r\n") - requests.push(sock.gets) - sock.print("+ idling\r\n") - rescue IOError # sock is closed by another thread - ensure - server.close - end - end - sleep 0.1 until started - threads << Thread.start do - imap = Net::IMAP.new(server_addr, :port => port) - begin - m = Monitor.new - in_idle = false - closed = false - c = m.new_cond - threads << Thread.start do - m.synchronize do - until in_idle - c.wait(0.1) - end - end - sock.close - m.synchronize do - closed = true - c.signal - end - end - assert_raise(EOFError) do - imap.idle do |res| - m.synchronize do - in_idle = true - c.signal - until closed - c.wait(0.1) - end - end - end - end - assert_equal(1, requests.length) - assert_equal("RUBY0001 IDLE\r\n", requests[0]) - ensure - imap.disconnect if imap - end - end - assert_join_threads(threads) - ensure - if sock && !sock.closed? - sock.close - end - end - - def test_connection_closed_without_greeting - server = create_tcp_server - port = server.addr[1] - start_server do - begin - sock = server.accept - sock.close - ensure - server.close - end - end - assert_raise(Net::IMAP::Error) do - Net::IMAP.new(server_addr, :port => port) - end - end - - def test_default_port - assert_equal(143, Net::IMAP.default_port) - assert_equal(143, Net::IMAP.default_imap_port) - assert_equal(993, Net::IMAP.default_tls_port) - assert_equal(993, Net::IMAP.default_ssl_port) - assert_equal(993, Net::IMAP.default_imaps_port) - end - - def test_send_invalid_number - server = create_tcp_server - port = server.addr[1] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - sock.gets - sock.print("RUBY0001 OK TEST completed\r\n") - sock.gets - sock.print("RUBY0002 OK TEST completed\r\n") - sock.gets - sock.print("RUBY0003 OK TEST completed\r\n") - sock.gets - sock.print("RUBY0004 OK TEST completed\r\n") - sock.gets - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0005 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - begin - imap = Net::IMAP.new(server_addr, :port => port) - assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", -1) - end - imap.send(:send_command, "TEST", 0) - imap.send(:send_command, "TEST", 4294967295) - assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", 4294967296) - end - assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(-1)) - end - assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(0)) - end - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(1)) - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967295)) - assert_raise(Net::IMAP::DataFormatError) do - imap.send(:send_command, "TEST", Net::IMAP::MessageSet.new(4294967296)) - end - imap.logout - ensure - imap.disconnect - end - end - - def test_send_literal - server = create_tcp_server - port = server.addr[1] - requests = [] - literal = nil - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - line = sock.gets - requests.push(line) - size = line.slice(/{(\d+)}\r\n/, 1).to_i - sock.print("+ Ready for literal data\r\n") - literal = sock.read(size) - requests.push(sock.gets) - sock.print("RUBY0001 OK TEST completed\r\n") - sock.gets - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0002 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - begin - imap = Net::IMAP.new(server_addr, :port => port) - imap.send(:send_command, "TEST", ["\xDE\xAD\xBE\xEF".b]) - assert_equal(2, requests.length) - assert_equal("RUBY0001 TEST ({4}\r\n", requests[0]) - assert_equal("\xDE\xAD\xBE\xEF".b, literal) - assert_equal(")\r\n", requests[1]) - imap.logout - ensure - imap.disconnect - end - end - - def test_disconnect - server = create_tcp_server - port = server.addr[1] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - sock.gets - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0001 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - begin - imap = Net::IMAP.new(server_addr, :port => port) - imap.logout - imap.disconnect - assert_equal(true, imap.disconnected?) - imap.disconnect - assert_equal(true, imap.disconnected?) - ensure - imap.disconnect if imap && !imap.disconnected? - end - end - - def test_append - server = create_tcp_server - port = server.addr[1] - mail = <<EOF.gsub(/\n/, "\r\n") -From: shugo@example.com -To: matz@example.com -Subject: hello - -hello world -EOF - requests = [] - received_mail = nil - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - line = sock.gets - requests.push(line) - size = line.slice(/{(\d+)}\r\n/, 1).to_i - sock.print("+ Ready for literal data\r\n") - received_mail = sock.read(size) - sock.gets - sock.print("RUBY0001 OK APPEND completed\r\n") - requests.push(sock.gets) - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0002 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - - begin - imap = Net::IMAP.new(server_addr, :port => port) - imap.append("INBOX", mail) - assert_equal(1, requests.length) - assert_equal("RUBY0001 APPEND INBOX {#{mail.size}}\r\n", requests[0]) - assert_equal(mail, received_mail) - imap.logout - assert_equal(2, requests.length) - assert_equal("RUBY0002 LOGOUT\r\n", requests[1]) - ensure - imap.disconnect if imap - end - end - - def test_append_fail - server = create_tcp_server - port = server.addr[1] - mail = <<EOF.gsub(/\n/, "\r\n") -From: shugo@example.com -To: matz@example.com -Subject: hello - -hello world -EOF - requests = [] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - requests.push(sock.gets) - sock.print("RUBY0001 NO Mailbox doesn't exist\r\n") - requests.push(sock.gets) - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0002 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - - begin - imap = Net::IMAP.new(server_addr, :port => port) - assert_raise(Net::IMAP::NoResponseError) do - imap.append("INBOX", mail) - end - assert_equal(1, requests.length) - assert_equal("RUBY0001 APPEND INBOX {#{mail.size}}\r\n", requests[0]) - imap.logout - assert_equal(2, requests.length) - assert_equal("RUBY0002 LOGOUT\r\n", requests[1]) - ensure - imap.disconnect if imap - end - end - - private - - def imaps_test - server = create_tcp_server - port = server.addr[1] - ctx = OpenSSL::SSL::SSLContext.new - ctx.ca_file = CA_FILE - ctx.key = File.open(SERVER_KEY) { |f| - OpenSSL::PKey::RSA.new(f) - } - ctx.cert = File.open(SERVER_CERT) { |f| - OpenSSL::X509::Certificate.new(f) - } - ssl_server = OpenSSL::SSL::SSLServer.new(server, ctx) - started = false - ths = Thread.start do - Thread.current.report_on_exception = false # always join-ed - begin - started = true - sock = ssl_server.accept - begin - sock.print("* OK test server\r\n") - sock.gets - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0001 OK LOGOUT completed\r\n") - ensure - sock.close - end - rescue Errno::EPIPE, Errno::ECONNRESET, Errno::ECONNABORTED - end - end - sleep 0.1 until started - begin - begin - imap = yield(port) - imap.logout - ensure - imap.disconnect if imap - end - ensure - ssl_server.close - ths.join - end - end - - def starttls_test - server = create_tcp_server - port = server.addr[1] - start_server do - sock = server.accept - begin - sock.print("* OK test server\r\n") - sock.gets - sock.print("RUBY0001 OK completed\r\n") - ctx = OpenSSL::SSL::SSLContext.new - ctx.ca_file = CA_FILE - ctx.key = File.open(SERVER_KEY) { |f| - OpenSSL::PKey::RSA.new(f) - } - ctx.cert = File.open(SERVER_CERT) { |f| - OpenSSL::X509::Certificate.new(f) - } - sock = OpenSSL::SSL::SSLSocket.new(sock, ctx) - sock.sync_close = true - sock.accept - sock.gets - sock.print("* BYE terminating connection\r\n") - sock.print("RUBY0002 OK LOGOUT completed\r\n") - ensure - sock.close - server.close - end - end - begin - imap = yield(port) - imap.logout if !imap.disconnected? - ensure - imap.disconnect if imap && !imap.disconnected? - end - end - - def create_tcp_server - return TCPServer.new(server_addr, 0) - end - - def server_addr - Addrinfo.tcp("localhost", 0).ip_address - end -end diff --git a/test/net/imap/test_imap_response_parser.rb b/test/net/imap/test_imap_response_parser.rb deleted file mode 100644 index ed31a03f5a..0000000000 --- a/test/net/imap/test_imap_response_parser.rb +++ /dev/null @@ -1,324 +0,0 @@ -# frozen_string_literal: true - -require "net/imap" -require "test/unit" - -class IMAPResponseParserTest < Test::Unit::TestCase - def setup - @do_not_reverse_lookup = Socket.do_not_reverse_lookup - Socket.do_not_reverse_lookup = true - if Net::IMAP.respond_to?(:max_flag_count) - @max_flag_count = Net::IMAP.max_flag_count - Net::IMAP.max_flag_count = 3 - end - end - - def teardown - Socket.do_not_reverse_lookup = @do_not_reverse_lookup - if Net::IMAP.respond_to?(:max_flag_count) - Net::IMAP.max_flag_count = @max_flag_count - end - end - - def test_flag_list_safe - parser = Net::IMAP::ResponseParser.new - response = lambda { - $SAFE = 1 - parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* LIST (\\HasChildren) "." "INBOX" -EOF - }.call - assert_equal [:Haschildren], response.data.attr - ensure - $SAFE = 0 - end - - def test_flag_list_too_many_flags - parser = Net::IMAP::ResponseParser.new - assert_nothing_raised do - 3.times do |i| - parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* LIST (\\Foo#{i}) "." "INBOX" -EOF - end - end - assert_raise(Net::IMAP::FlagCountError) do - parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* LIST (\\Foo3) "." "INBOX" -EOF - end - end - - def test_flag_list_many_same_flags - parser = Net::IMAP::ResponseParser.new - assert_nothing_raised do - 100.times do - parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* LIST (\\Foo) "." "INBOX" -EOF - end - end - end - - def test_flag_xlist_inbox - parser = Net::IMAP::ResponseParser.new - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* XLIST (\\Inbox) "." "INBOX" -EOF - assert_equal [:Inbox], response.data.attr - end - - def test_resp_text_code - parser = Net::IMAP::ResponseParser.new - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* OK [CLOSED] Previous mailbox closed. -EOF - assert_equal "CLOSED", response.data.code.name - end - - def test_search_response - parser = Net::IMAP::ResponseParser.new - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* SEARCH -EOF - assert_equal [], response.data - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* SEARCH 1 -EOF - assert_equal [1], response.data - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* SEARCH 1 2 3 -EOF - assert_equal [1, 2, 3], response.data - end - - def test_search_response_of_yahoo - parser = Net::IMAP::ResponseParser.new - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* SEARCH 1\s -EOF - assert_equal [1], response.data - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* SEARCH 1 2 3\s -EOF - assert_equal [1, 2, 3], response.data - end - - def test_msg_att_extra_space - parser = Net::IMAP::ResponseParser.new - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* 1 FETCH (UID 92285) -EOF - assert_equal 92285, response.data.attr["UID"] - - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* 1 FETCH (UID 92285 ) -EOF - assert_equal 92285, response.data.attr["UID"] - end - - def test_msg_att_parse_error - parser = Net::IMAP::ResponseParser.new - e = assert_raise(Net::IMAP::ResponseParseError) { - parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* 123 FETCH (UNKNOWN 92285) -EOF - } - assert_match(/ for \{123\}/, e.message) - end - - def test_msg_att_rfc822_text - parser = Net::IMAP::ResponseParser.new - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* 123 FETCH (RFC822 {5} -foo -) -EOF - assert_equal("foo\r\n", response.data.attr["RFC822"]) - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* 123 FETCH (RFC822[] {5} -foo -) -EOF - assert_equal("foo\r\n", response.data.attr["RFC822"]) - end - - # [Bug #6397] [ruby-core:44849] - def test_body_type_attachment - parser = Net::IMAP::ResponseParser.new - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* 980 FETCH (UID 2862 BODYSTRUCTURE ((("TEXT" "PLAIN" ("CHARSET" "iso-8859-1") NIL NIL "7BIT" 416 21 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "iso-8859-1") NIL NIL "7BIT" 1493 32 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "Boundary_(ID_IaecgfnXwG5bn3x8lIeGIQ)") NIL NIL)("MESSAGE" "RFC822" ("NAME" "Fw_ ____ _____ ____.eml") NIL NIL "7BIT" 1980088 NIL ("ATTACHMENT" ("FILENAME" "Fw_ ____ _____ ____.eml")) NIL) "MIXED" ("BOUNDARY" "Boundary_(ID_eDdLc/j0mBIzIlR191pHjA)") NIL NIL)) -EOF - assert_equal("Fw_ ____ _____ ____.eml", - response.data.attr["BODYSTRUCTURE"].parts[1].body.param["FILENAME"]) - end - - def assert_parseable(s) - parser = Net::IMAP::ResponseParser.new - parser.parse(s.gsub(/\n/, "\r\n").taint) - end - - # [Bug #7146] - def test_msg_delivery_status - # This was part of a larger response that caused crashes, but this was the - # minimal test case to demonstrate it - assert_parseable <<EOF -* 4902 FETCH (BODY (("MESSAGE" "DELIVERY-STATUS" NIL NIL NIL "7BIT" 324) "REPORT")) -EOF - end - - # [Bug #7147] - def test_msg_with_message_rfc822_attachment - assert_parseable <<EOF -* 5441 FETCH (BODY ((("TEXT" "PLAIN" ("CHARSET" "iso-8859-1") NIL NIL "QUOTED-PRINTABLE" 69 1)("TEXT" "HTML" ("CHARSET" "iso-8859-1") NIL NIL "QUOTED-PRINTABLE" 455 12) "ALTERNATIVE")("MESSAGE" "RFC822" ("NAME" "ATT00026.eml") NIL NIL "7BIT" 4079755) "MIXED")) -EOF - end - - # [Bug #7153] - def test_msg_body_mixed - assert_parseable <<EOF -* 1038 FETCH (BODY ("MIXED")) -EOF - end - - # [Bug #8167] - def test_msg_delivery_status_with_extra_data - parser = Net::IMAP::ResponseParser.new - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* 29021 FETCH (RFC822.SIZE 3162 UID 113622 RFC822.HEADER {1155} -Return-path: <> -Envelope-to: info@xxxxxxxx.si -Delivery-date: Tue, 26 Mar 2013 12:42:58 +0100 -Received: from mail by xxxx.xxxxxxxxxxx.net with spam-scanned (Exim 4.76) - id 1UKSHI-000Cwl-AR - for info@xxxxxxxx.si; Tue, 26 Mar 2013 12:42:58 +0100 -X-Spam-Checker-Version: SpamAssassin 3.3.1 (2010-03-16) on xxxx.xxxxxxxxxxx.net -X-Spam-Level: ** -X-Spam-Status: No, score=2.1 required=7.0 tests=DKIM_ADSP_NXDOMAIN,RDNS_NONE - autolearn=no version=3.3.1 -Received: from [xx.xxx.xxx.xx] (port=56890 helo=xxxxxx.localdomain) - by xxxx.xxxxxxxxxxx.net with esmtp (Exim 4.76) - id 1UKSHI-000Cwi-9j - for info@xxxxxxxx.si; Tue, 26 Mar 2013 12:42:56 +0100 -Received: by xxxxxx.localdomain (Postfix) - id 72725BEA64A; Tue, 26 Mar 2013 12:42:55 +0100 (CET) -Date: Tue, 26 Mar 2013 12:42:55 +0100 (CET) -From: MAILER-DAEMON@xxxxxx.localdomain (Mail Delivery System) -Subject: Undelivered Mail Returned to Sender -To: info@xxxxxxxx.si -Auto-Submitted: auto-replied -MIME-Version: 1.0 -Content-Type: multipart/report; report-type=delivery-status; - boundary="27797BEA649.1364298175/xxxxxx.localdomain" -Message-Id: <20130326114255.72725BEA64A@xxxxxx.localdomain> - - BODYSTRUCTURE (("text" "plain" ("charset" "us-ascii") NIL "Notification" "7bit" 510 14 NIL NIL NIL NIL)("message" "delivery-status" NIL NIL "Delivery report" "7bit" 410 NIL NIL NIL NIL)("text" "rfc822-headers" ("charset" "us-ascii") NIL "Undelivered Message Headers" "7bit" 612 15 NIL NIL NIL NIL) "report" ("report-type" "delivery-status" "boundary" "27797BEA649.1364298175/xxxxxx.localdomain") NIL NIL NIL)) -EOF - delivery_status = response.data.attr["BODYSTRUCTURE"].parts[1] - assert_equal("MESSAGE", delivery_status.media_type) - assert_equal("DELIVERY-STATUS", delivery_status.subtype) - assert_equal(nil, delivery_status.param) - assert_equal(nil, delivery_status.content_id) - assert_equal("Delivery report", delivery_status.description) - assert_equal("7BIT", delivery_status.encoding) - assert_equal(410, delivery_status.size) - end - - # [Bug #8281] - def test_acl - parser = Net::IMAP::ResponseParser.new - response = parser.parse(<<EOF.gsub(/\n/, "\r\n").taint) -* ACL "INBOX/share" "imshare2copy1366146467@xxxxxxxxxxxxxxxxxx.com" lrswickxteda -EOF - assert_equal("ACL", response.name) - assert_equal(1, response.data.length) - assert_equal("INBOX/share", response.data[0].mailbox) - assert_equal("imshare2copy1366146467@xxxxxxxxxxxxxxxxxx.com", - response.data[0].user) - assert_equal("lrswickxteda", response.data[0].rights) - end - - # [Bug #8415] - def test_capability - parser = Net::IMAP::ResponseParser.new - response = parser.parse("* CAPABILITY st11p00mm-iscream009 1Q49 XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 SASL-IR AUTH=ATOKEN AUTH=PLAIN\r\n") - assert_equal("CAPABILITY", response.name) - assert_equal("AUTH=PLAIN", response.data.last) - response = parser.parse("* CAPABILITY st11p00mm-iscream009 1Q49 XAPPLEPUSHSERVICE IMAP4 IMAP4rev1 SASL-IR AUTH=ATOKEN AUTH=PLAIN \r\n") - assert_equal("CAPABILITY", response.name) - assert_equal("AUTH=PLAIN", response.data.last) - end - - def test_mixed_boundary - parser = Net::IMAP::ResponseParser.new - response = parser.parse("* 2688 FETCH (UID 179161 BODYSTRUCTURE (" \ - "(\"TEXT\" \"PLAIN\" (\"CHARSET\" \"iso-8859-1\") NIL NIL \"QUOTED-PRINTABLE\" 200 4 NIL NIL NIL)" \ - "(\"MESSAGE\" \"DELIVERY-STATUS\" NIL NIL NIL \"7BIT\" 318 NIL NIL NIL)" \ - "(\"MESSAGE\" \"RFC822\" NIL NIL NIL \"7BIT\" 2177" \ - " (\"Tue, 11 May 2010 18:28:16 -0400\" \"Re: Welcome letter\" (" \ - "(\"David\" NIL \"info\" \"xxxxxxxx.si\")) " \ - "((\"David\" NIL \"info\" \"xxxxxxxx.si\")) " \ - "((\"David\" NIL \"info\" \"xxxxxxxx.si\")) " \ - "((\"Doretha\" NIL \"doretha.info\" \"xxxxxxxx.si\")) " \ - "NIL NIL " \ - "\"<AC1D15E06EA82F47BDE18E851CC32F330717704E@localdomain>\" " \ - "\"<AANLkTikKMev1I73L2E7XLjRs67IHrEkb23f7ZPmD4S_9@localdomain>\")" \ - " (\"MIXED\" (\"BOUNDARY\" \"000e0cd29212e3e06a0486590ae2\") NIL NIL)" \ - " 37 NIL NIL NIL)" \ - " \"REPORT\" (\"BOUNDARY\" \"16DuG.4XbaNOvCi.9ggvq.8Ipnyp3\" \"REPORT-TYPE\" \"delivery-status\") NIL NIL))\r\n") - empty_part = response.data.attr['BODYSTRUCTURE'].parts[2] - assert_equal(empty_part.lines, 37) - assert_equal(empty_part.body.media_type, 'MULTIPART') - assert_equal(empty_part.body.subtype, 'MIXED') - assert_equal(empty_part.body.param['BOUNDARY'], '000e0cd29212e3e06a0486590ae2') - end - - # [Bug #10112] - def test_search_modseq - parser = Net::IMAP::ResponseParser.new - response = parser.parse("* SEARCH 87216 87221 (MODSEQ 7667567)\r\n") - assert_equal("SEARCH", response.name) - assert_equal([87216, 87221], response.data) - end - - # [Bug #11128] - def test_body_ext_mpart_without_lang - parser = Net::IMAP::ResponseParser.new - response = parser.parse("* 4 FETCH (BODY (((\"text\" \"plain\" (\"charset\" \"utf-8\") NIL NIL \"7bit\" 257 9 NIL NIL NIL NIL)(\"text\" \"html\" (\"charset\" \"utf-8\") NIL NIL \"quoted-printable\" 655 9 NIL NIL NIL NIL) \"alternative\" (\"boundary\" \"001a1137a5047848dd05157ddaa1\") NIL)(\"application\" \"pdf\" (\"name\" \"test.xml\" \"x-apple-part-url\" \"9D00D9A2-98AB-4EFB-85BA-FB255F8BF3D7\") NIL NIL \"base64\" 4383638 NIL (\"attachment\" (\"filename\" \"test.xml\")) NIL NIL) \"mixed\" (\"boundary\" \"001a1137a5047848e405157ddaa3\") NIL))\r\n") - assert_equal("FETCH", response.name) - body = response.data.attr["BODY"] - assert_equal(nil, body.parts[0].disposition) - assert_equal(nil, body.parts[0].language) - assert_equal("ATTACHMENT", body.parts[1].disposition.dsp_type) - assert_equal("test.xml", body.parts[1].disposition.param["FILENAME"]) - assert_equal(nil, body.parts[1].language) - end - - # [Bug #13649] - def test_status - parser = Net::IMAP::ResponseParser.new - response = parser.parse("* STATUS INBOX (UIDNEXT 1 UIDVALIDITY 1234)\r\n") - assert_equal("STATUS", response.name) - assert_equal("INBOX", response.data.mailbox) - assert_equal(1234, response.data.attr["UIDVALIDITY"]) - response = parser.parse("* STATUS INBOX (UIDNEXT 1 UIDVALIDITY 1234) \r\n") - assert_equal("STATUS", response.name) - assert_equal("INBOX", response.data.mailbox) - assert_equal(1234, response.data.attr["UIDVALIDITY"]) - end - - # [Bug #10119] - def test_msg_att_modseq_data - parser = Net::IMAP::ResponseParser.new - response = parser.parse("* 1 FETCH (FLAGS (\Seen) MODSEQ (12345) UID 5)\r\n") - assert_equal(12345, response.data.attr["MODSEQ"]) - end - - def test_continuation_request_without_response_text - parser = Net::IMAP::ResponseParser.new - response = parser.parse("+\r\n") - assert_instance_of(Net::IMAP::ContinuationRequest, response) - assert_equal(nil, response.data.code) - assert_equal("", response.data.text) - end -end diff --git a/test/net/pop/test_pop.rb b/test/net/pop/test_pop.rb deleted file mode 100644 index f4c807a7a8..0000000000 --- a/test/net/pop/test_pop.rb +++ /dev/null @@ -1,166 +0,0 @@ -# frozen_string_literal: true -require 'net/pop' -require 'test/unit' -require 'digest/md5' - -class TestPOP < Test::Unit::TestCase - def setup - @users = {'user' => 'pass' } - @ok_user = 'user' - @stamp_base = "#{$$}.#{Time.now.to_i}@localhost" - end - - def test_pop_auth_ok - pop_test(false) do |pop| - assert_instance_of Net::POP3, pop - assert_nothing_raised do - pop.start(@ok_user, @users[@ok_user]) - end - end - end - - def test_pop_auth_ng - pop_test(false) do |pop| - assert_instance_of Net::POP3, pop - assert_raise Net::POPAuthenticationError do - pop.start(@ok_user, 'bad password') - end - end - end - - def test_apop_ok - pop_test(@stamp_base) do |pop| - assert_instance_of Net::APOP, pop - assert_nothing_raised do - pop.start(@ok_user, @users[@ok_user]) - end - end - end - - def test_apop_ng - pop_test(@stamp_base) do |pop| - assert_instance_of Net::APOP, pop - assert_raise Net::POPAuthenticationError do - pop.start(@ok_user, 'bad password') - end - end - end - - def test_apop_invalid - pop_test("\x80"+@stamp_base) do |pop| - assert_instance_of Net::APOP, pop - assert_raise Net::POPAuthenticationError do - pop.start(@ok_user, @users[@ok_user]) - end - end - end - - def test_apop_invalid_at - pop_test(@stamp_base.sub('@', '.')) do |pop| - assert_instance_of Net::APOP, pop - assert_raise Net::POPAuthenticationError do - pop.start(@ok_user, @users[@ok_user]) - end - end - end - - def test_popmail - # totally not representative of real messages, but - # enough to test frozen bugs - lines = [ "[ruby-core:85210]" , "[Bug #14416]" ].freeze - command = Object.new - command.instance_variable_set(:@lines, lines) - - def command.retr(n) - @lines.each { |l| yield "#{l}\r\n" } - end - - def command.top(number, nl) - @lines.each do |l| - yield "#{l}\r\n" - break if (nl -= 1) <= 0 - end - end - - net_pop = :unused - popmail = Net::POPMail.new(1, 123, net_pop, command) - res = popmail.pop - assert_equal "[ruby-core:85210]\r\n[Bug #14416]\r\n", res - assert_not_predicate res, :frozen? - - res = popmail.top(1) - assert_equal "[ruby-core:85210]\r\n", res - assert_not_predicate res, :frozen? - end - - def pop_test(apop=false) - host = 'localhost' - server = TCPServer.new(host, 0) - port = server.addr[1] - server_thread = Thread.start do - sock = server.accept - begin - pop_server_loop(sock, apop) - ensure - sock.close - end - end - client_thread = Thread.start do - begin - begin - pop = Net::POP3::APOP(apop).new(host, port) - #pop.set_debug_output $stderr - yield pop - ensure - begin - pop.finish - rescue IOError - raise unless $!.message == "POP session not yet started" - end - end - ensure - server.close - end - end - assert_join_threads([client_thread, server_thread]) - end - - def pop_server_loop(sock, apop) - if apop - sock.print "+OK ready <#{apop}>\r\n" - else - sock.print "+OK ready\r\n" - end - user = nil - while line = sock.gets - case line - when /^USER (.+)\r\n/ - user = $1 - if @users.key?(user) - sock.print "+OK\r\n" - else - sock.print "-ERR unknown user\r\n" - end - when /^PASS (.+)\r\n/ - if @users[user] == $1 - sock.print "+OK\r\n" - else - sock.print "-ERR invalid password\r\n" - end - when /^APOP (.+) (.+)\r\n/ - user = $1 - if apop && Digest::MD5.hexdigest("<#{apop}>#{@users[user]}") == $2 - sock.print "+OK\r\n" - else - sock.print "-ERR authentication failed\r\n" - end - when /^QUIT/ - sock.print "+OK bye\r\n" - return - else - sock.print "-ERR command not recognized\r\n" - return - end - end - end -end diff --git a/test/net/protocol/test_protocol.rb b/test/net/protocol/test_protocol.rb index d3dc2ccf4c..2f42fa3236 100644 --- a/test/net/protocol/test_protocol.rb +++ b/test/net/protocol/test_protocol.rb @@ -57,6 +57,14 @@ class TestProtocol < Test::Unit::TestCase mockio end + def test_readuntil + assert_output("", "") do + sio = StringIO.new("12345".dup) + io = Net::BufferedIO.new(sio) + assert_equal "12345", io.readuntil("5") + end + end + def test_write0_multibyte mockio = create_mockio(max: 1) io = Net::BufferedIO.new(mockio) @@ -119,4 +127,33 @@ class TestProtocol < Test::Unit::TestCase io.write_timeout = 0.1 assert_raise(Net::WriteTimeout){ io.write("a"*50,"a"*50,"a") } end + + class FakeReadPartialIO + def initialize(chunks) + @chunks = chunks.map(&:dup) + end + + def read_nonblock(size, buf = nil, exception: false) + if buf + buf.replace(@chunks.shift) + buf + else + @chunks.shift + end + end + end + + def test_shareable_buffer_leak # https://github.com/ruby/net-protocol/pull/19 + expected_chunks = [ + "aaaaa", + "bbbbb", + ] + fake_io = FakeReadPartialIO.new(expected_chunks) + io = Net::BufferedIO.new(fake_io) + actual_chunks = [] + reader = Net::ReadAdapter.new(-> (chunk) { actual_chunks << chunk }) + io.read(5, reader) + io.read(5, reader) + assert_equal expected_chunks, actual_chunks + end end diff --git a/test/net/smtp/test_response.rb b/test/net/smtp/test_response.rb deleted file mode 100644 index 3cf909a762..0000000000 --- a/test/net/smtp/test_response.rb +++ /dev/null @@ -1,100 +0,0 @@ -# frozen_string_literal: true -require 'net/smtp' -require 'test/unit' - -module Net - class SMTP - class TestResponse < Test::Unit::TestCase - def test_capabilities - res = Response.parse("250-ubuntu-desktop\n250-PIPELINING\n250-SIZE 10240000\n250-VRFY\n250-ETRN\n250-STARTTLS\n250-ENHANCEDSTATUSCODES\n250 DSN\n") - - capabilities = res.capabilities - %w{ PIPELINING SIZE VRFY STARTTLS ENHANCEDSTATUSCODES DSN}.each do |str| - assert capabilities.key?(str), str - end - end - - def test_capabilities_default - res = Response.parse("250-ubuntu-desktop\n250-PIPELINING\n250 DSN\n") - assert_equal [], res.capabilities['PIPELINING'] - end - - def test_capabilities_value - res = Response.parse("250-ubuntu-desktop\n250-SIZE 1234\n250 DSN\n") - assert_equal ['1234'], res.capabilities['SIZE'] - end - - def test_capabilities_multi - res = Response.parse("250-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n") - assert_equal %w{1 2 3}, res.capabilities['SIZE'] - end - - def test_bad_string - res = Response.parse("badstring") - assert_equal({}, res.capabilities) - end - - def test_success? - res = Response.parse("250-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n") - assert res.success? - assert !res.continue? - end - - # RFC 2821, Section 4.2.1 - def test_continue? - res = Response.parse("3yz-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n") - assert !res.success? - assert res.continue? - end - - def test_status_type_char - res = Response.parse("3yz-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n") - assert_equal '3', res.status_type_char - - res = Response.parse("250-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n") - assert_equal '2', res.status_type_char - end - - def test_message - res = Response.parse("250-ubuntu-desktop\n250-SIZE 1 2 3\n250 DSN\n") - assert_equal "250-ubuntu-desktop\n", res.message - end - - def test_server_busy_exception - res = Response.parse("400 omg busy") - assert_equal Net::SMTPServerBusy, res.exception_class - res = Response.parse("410 omg busy") - assert_equal Net::SMTPServerBusy, res.exception_class - end - - def test_syntax_error_exception - res = Response.parse("500 omg syntax error") - assert_equal Net::SMTPSyntaxError, res.exception_class - - res = Response.parse("501 omg syntax error") - assert_equal Net::SMTPSyntaxError, res.exception_class - end - - def test_authentication_exception - res = Response.parse("530 omg auth error") - assert_equal Net::SMTPAuthenticationError, res.exception_class - - res = Response.parse("531 omg auth error") - assert_equal Net::SMTPAuthenticationError, res.exception_class - end - - def test_fatal_error - res = Response.parse("510 omg fatal error") - assert_equal Net::SMTPFatalError, res.exception_class - - res = Response.parse("511 omg fatal error") - assert_equal Net::SMTPFatalError, res.exception_class - end - - def test_default_exception - res = Response.parse("250 omg fatal error") - assert_equal Net::SMTPUnknownError, res.exception_class - end - end - end -end diff --git a/test/net/smtp/test_smtp.rb b/test/net/smtp/test_smtp.rb deleted file mode 100644 index 90c92e06f8..0000000000 --- a/test/net/smtp/test_smtp.rb +++ /dev/null @@ -1,200 +0,0 @@ -# frozen_string_literal: true -require 'net/smtp' -require 'stringio' -require 'test/unit' - -module Net - class TestSMTP < Test::Unit::TestCase - CA_FILE = File.expand_path("../fixtures/cacert.pem", __dir__) - SERVER_KEY = File.expand_path("../fixtures/server.key", __dir__) - SERVER_CERT = File.expand_path("../fixtures/server.crt", __dir__) - - class FakeSocket - attr_reader :write_io - - def initialize out = "250 OK\n" - @write_io = StringIO.new - @read_io = StringIO.new out - end - - def writeline line - @write_io.write "#{line}\r\n" - end - - def readline - line = @read_io.gets - raise 'ran out of input' unless line - line.chop - end - end - - def test_critical - smtp = Net::SMTP.new 'localhost', 25 - - assert_raise RuntimeError do - smtp.send :critical do - raise 'fail on purpose' - end - end - - assert_kind_of Net::SMTP::Response, smtp.send(:critical), - '[Bug #9125]' - end - - def test_esmtp - smtp = Net::SMTP.new 'localhost', 25 - assert smtp.esmtp - assert smtp.esmtp? - - smtp.esmtp = 'omg' - assert_equal 'omg', smtp.esmtp - assert_equal 'omg', smtp.esmtp? - end - - def test_rset - smtp = Net::SMTP.new 'localhost', 25 - smtp.instance_variable_set :@socket, FakeSocket.new - - assert smtp.rset - end - - def test_mailfrom - sock = FakeSocket.new - smtp = Net::SMTP.new 'localhost', 25 - smtp.instance_variable_set :@socket, sock - assert smtp.mailfrom("foo@example.com").success? - assert_equal "MAIL FROM:<foo@example.com>\r\n", sock.write_io.string - end - - def test_rcptto - sock = FakeSocket.new - smtp = Net::SMTP.new 'localhost', 25 - smtp.instance_variable_set :@socket, sock - assert smtp.rcptto("foo@example.com").success? - assert_equal "RCPT TO:<foo@example.com>\r\n", sock.write_io.string - end - - def test_auth_plain - sock = FakeSocket.new - smtp = Net::SMTP.new 'localhost', 25 - smtp.instance_variable_set :@socket, sock - assert smtp.auth_plain("foo", "bar").success? - assert_equal "AUTH PLAIN AGZvbwBiYXI=\r\n", sock.write_io.string - end - - def test_crlf_injection - smtp = Net::SMTP.new 'localhost', 25 - smtp.instance_variable_set :@socket, FakeSocket.new - - assert_raise(ArgumentError) do - smtp.mailfrom("foo\r\nbar") - end - - assert_raise(ArgumentError) do - smtp.mailfrom("foo\rbar") - end - - assert_raise(ArgumentError) do - smtp.mailfrom("foo\nbar") - end - - assert_raise(ArgumentError) do - smtp.rcptto("foo\r\nbar") - end - end - - def test_tls_connect - servers = Socket.tcp_server_sockets("localhost", 0) - ctx = OpenSSL::SSL::SSLContext.new - ctx.ca_file = CA_FILE - ctx.key = File.open(SERVER_KEY) { |f| - OpenSSL::PKey::RSA.new(f) - } - ctx.cert = File.open(SERVER_CERT) { |f| - OpenSSL::X509::Certificate.new(f) - } - begin - sock = nil - Thread.start do - s = accept(servers) - sock = OpenSSL::SSL::SSLSocket.new(s, ctx) - sock.sync_close = true - sock.accept - sock.write("220 localhost Service ready\r\n") - sock.gets - sock.write("250 localhost\r\n") - sock.gets - sock.write("221 localhost Service closing transmission channel\r\n") - end - smtp = Net::SMTP.new("localhost", servers[0].local_address.ip_port) - smtp.enable_tls - smtp.open_timeout = 1 - smtp.start do - end - ensure - sock.close if sock - servers.each(&:close) - end - rescue LoadError - # skip (require openssl) - end - - def test_tls_connect_timeout - servers = Socket.tcp_server_sockets("localhost", 0) - begin - sock = nil - Thread.start do - sock = accept(servers) - end - smtp = Net::SMTP.new("localhost", servers[0].local_address.ip_port) - smtp.enable_tls - smtp.open_timeout = 0.1 - assert_raise(Net::OpenTimeout) do - smtp.start do - end - end - rescue LoadError - # skip (require openssl) - ensure - sock.close if sock - servers.each(&:close) - end - end - - def test_eof_error_backtrace - bug13018 = '[ruby-core:78550] [Bug #13018]' - servers = Socket.tcp_server_sockets("localhost", 0) - begin - sock = nil - t = Thread.start do - sock = accept(servers) - sock.close - end - smtp = Net::SMTP.new("localhost", servers[0].local_address.ip_port) - e = assert_raise(EOFError, bug13018) do - smtp.start do - end - end - assert_equal(EOFError, e.class, bug13018) - assert(e.backtrace.grep(%r"\bnet/smtp\.rb:").size > 0, bug13018) - ensure - sock.close if sock - servers.each(&:close) - t.join - end - end - - private - - def accept(servers) - loop do - readable, = IO.select(servers.map(&:to_io)) - readable.each do |r| - sock, = r.accept_nonblock(exception: false) - next if sock == :wait_readable - return sock - end - end - end - end -end diff --git a/test/net/smtp/test_ssl_socket.rb b/test/net/smtp/test_ssl_socket.rb deleted file mode 100644 index 342391f159..0000000000 --- a/test/net/smtp/test_ssl_socket.rb +++ /dev/null @@ -1,97 +0,0 @@ -# frozen_string_literal: true -require 'net/smtp' -require 'test/unit' - -module Net - class TestSSLSocket < Test::Unit::TestCase - class MySMTP < SMTP - attr_accessor :fake_tcp, :fake_ssl - - def initialize(*args) - super(*args) - @open_timeout = nil - end - - def tcp_socket address, port - fake_tcp - end - - def ssl_socket socket, context - fake_ssl - end - end - - require 'stringio' - class SSLSocket < StringIO - attr_accessor :sync_close, :connected, :closed - - def initialize(*args) - @connected = false - @closed = true - super - end - - def connect - self.connected = true - self.closed = false - end - - def close - self.closed = true - end - - def post_connection_check omg - end - end - - def test_ssl_socket_close_on_post_connection_check_fail - tcp_socket = StringIO.new success_response - - ssl_socket = SSLSocket.new.extend Module.new { - def post_connection_check omg - raise OpenSSL::SSL::SSLError, 'hostname was not match with the server certificate' - end - } - - connection = MySMTP.new('localhost', 25) - connection.enable_starttls_auto - connection.fake_tcp = tcp_socket - connection.fake_ssl = ssl_socket - - assert_raise(OpenSSL::SSL::SSLError) do - connection.start - end - assert_equal true, ssl_socket.closed - end - - def test_ssl_socket_open_on_post_connection_check_success - tcp_socket = StringIO.new success_response - - ssl_socket = SSLSocket.new success_response - - connection = MySMTP.new('localhost', 25) - connection.enable_starttls_auto - connection.fake_tcp = tcp_socket - connection.fake_ssl = ssl_socket - - connection.start - assert_equal false, ssl_socket.closed - end - - def success_response - [ - '220 smtp.example.com ESMTP Postfix', - "250-ubuntu-desktop", - "250-PIPELINING", - "250-SIZE 10240000", - "250-VRFY", - "250-ETRN", - "250-STARTTLS", - "250-ENHANCEDSTATUSCODES", - "250-8BITMIME", - "250 DSN", - "220 2.0.0 Ready to start TLS", - ].join("\r\n") + "\r\n" - end - end -end if defined?(OpenSSL) |
