├── tests ├── ssl │ ├── ca │ │ ├── ca.crl │ │ ├── ca.srl │ │ ├── server.key │ │ ├── server.cnf │ │ ├── server.csr │ │ ├── client.cnf │ │ ├── localhost.cnf │ │ ├── ca.cnf │ │ ├── gen-localhost.sh │ │ ├── gen-client.sh │ │ ├── ca.csr │ │ ├── localhost.js │ │ ├── server.js │ │ ├── server.crt │ │ ├── ca.key │ │ ├── ca.crt │ │ ├── client.csr │ │ ├── localhost.csr │ │ ├── client.crt │ │ ├── localhost.crt │ │ ├── client.key │ │ ├── localhost.key │ │ └── client-enc.key │ ├── test.crt │ ├── test.key │ └── npm-ca.crt ├── unicycle.jpg ├── googledoodle.jpg ├── test-form-data-error.js ├── browser │ ├── ssl │ │ ├── ca.crt │ │ ├── server.crt │ │ └── server.key │ ├── test.js │ ├── start.js │ └── karma.conf.js ├── test-event-forwarding.js ├── test-follow-all-303.js ├── test-toJSON.js ├── test-option-reuse.js ├── test-piped-redirect.js ├── test-follow-all.js ├── test-emptyBody.js ├── test-promise.js ├── test-hawk.js ├── test-localAddress.js ├── test-agentOptions.js ├── test-onelineproxy.js ├── test-timing.js ├── test-form-urlencoded.js ├── test-redirect-complex.js ├── test-proxy-connect.js ├── test-unix.js ├── test-json-request.js ├── test-rfc3986.js ├── test-params.js ├── test-errors.js ├── squid.conf ├── test-isUrl.js ├── test-node-debug.js ├── test-agent.js ├── test-cookies.js ├── test-form.js ├── test-httpModule.js ├── fixtures │ └── har.json ├── test-https.js ├── test-redirect-auth.js ├── test-multipart.js ├── test-timeout.js ├── test-pool.js ├── test-body.js ├── test-baseUrl.js ├── test-qs.js ├── server.js ├── test-multipart-encoding.js ├── test-har.js ├── test-http-signature.js ├── test-headers.js ├── test-form-data.js ├── test-bearer-auth.js ├── test-basic-auth.js ├── core.js ├── test-gzip.js ├── test-digest-auth.js └── test-proxy.js ├── .gitignore ├── .editorconfig ├── README.md ├── lib ├── params.js ├── defaults.js ├── base-url.js ├── cookie.js ├── getProxyFromURI.js ├── options.js └── har.js ├── package.json └── index.js /tests/ssl/ca/ca.crl: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /tests/ssl/ca/ca.srl: -------------------------------------------------------------------------------- 1 | ADF62016AA40C9C3 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | npm-debug.log 2 | node_modules/ 3 | -------------------------------------------------------------------------------- /tests/unicycle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/request/legacy/master/tests/unicycle.jpg -------------------------------------------------------------------------------- /tests/googledoodle.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/request/legacy/master/tests/googledoodle.jpg -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # @request/legacy 3 | 4 | > See [request][request] for more details. 5 | 6 | 7 | ## Notice 8 | 9 | This module may contain code snippets initially implemented in [request][request] by [request contributors][request-contributors]. 10 | 11 | 12 | [request]: https://github.com/request/request 13 | [request-contributors]: https://github.com/request/request/graphs/contributors 14 | -------------------------------------------------------------------------------- /tests/ssl/ca/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIBOwIBAAJBAOBWXSMy6a86mYzbRbm/3KEaBmPyE+ERAX83vIIFUGf+tYZibvQg 3 | cLxP+lHlzQuRZzmB2cIkS8pZCOEMErFkPwUCAwEAAQJAK+r8ZM2sze8s7FRo/ApB 4 | iRBtO9fCaIdJwbwJnXKo4RKwZDt1l2mm+fzZ+/QaQNjY1oTROkIIXmnwRvZWfYlW 5 | gQIhAPKYsG+YSBN9o8Sdp1DMyZ/rUifKX3OE6q9tINkgajDVAiEA7Ltqh01+cnt0 6 | JEnud/8HHcuehUBLMofeg0G+gCnSbXECIQCqDvkXsWNNLnS/3lgsnvH0Baz4sbeJ 7 | rjIpuVEeg8eM5QIgbu0+9JmOV6ybdmmiMV4yAncoF35R/iKGVHDZCAsQzDECIQDZ 8 | 0jGz22tlo5YMcYSqrdD3U4sds1pwiAaWFRbCunoUJw== 9 | -----END RSA PRIVATE KEY----- 10 | -------------------------------------------------------------------------------- /lib/params.js: -------------------------------------------------------------------------------- 1 | 2 | var extend = require('extend') 3 | 4 | 5 | exports.init = function (uri, options, callback) { 6 | if (typeof options === 'function') { 7 | callback = options 8 | } 9 | 10 | var result = {} 11 | if (typeof options === 'object') { 12 | extend(result, options, {uri: uri}) 13 | } 14 | else if (typeof uri === 'string') { 15 | extend(result, {uri: uri}) 16 | } 17 | else { 18 | extend(result, uri) 19 | } 20 | 21 | if (callback) { 22 | result.callback = callback 23 | } 24 | return result 25 | } 26 | -------------------------------------------------------------------------------- /tests/ssl/ca/server.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 1024 3 | days = 3650 4 | distinguished_name = req_distinguished_name 5 | attributes = req_attributes 6 | prompt = no 7 | 8 | [ req_distinguished_name ] 9 | C = US 10 | ST = CA 11 | L = Oakland 12 | O = request 13 | OU = testing 14 | CN = testing.request.mikealrogers.com 15 | emailAddress = mikeal@mikealrogers.com 16 | 17 | [ req_attributes ] 18 | challengePassword = password challenge 19 | 20 | -------------------------------------------------------------------------------- /tests/ssl/ca/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIBgjCCASwCAQAwgaMxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEQMA4GA1UE 3 | BxMHT2FrbGFuZDEQMA4GA1UEChMHcmVxdWVzdDEQMA4GA1UECxMHdGVzdGluZzEp 4 | MCcGA1UEAxMgdGVzdGluZy5yZXF1ZXN0Lm1pa2VhbHJvZ2Vycy5jb20xJjAkBgkq 5 | hkiG9w0BCQEWF21pa2VhbEBtaWtlYWxyb2dlcnMuY29tMFwwDQYJKoZIhvcNAQEB 6 | BQADSwAwSAJBAOBWXSMy6a86mYzbRbm/3KEaBmPyE+ERAX83vIIFUGf+tYZibvQg 7 | cLxP+lHlzQuRZzmB2cIkS8pZCOEMErFkPwUCAwEAAaAjMCEGCSqGSIb3DQEJBzEU 8 | ExJwYXNzd29yZCBjaGFsbGVuZ2UwDQYJKoZIhvcNAQEFBQADQQBD3E5WekQzCEJw 9 | 7yOcqvtPYIxGaX8gRKkYfLPoj3pm3GF5SGqtJKhylKfi89szHXgktnQgzff9FN+A 10 | HidVJ/3u 11 | -----END CERTIFICATE REQUEST----- 12 | -------------------------------------------------------------------------------- /tests/ssl/ca/client.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 1024 3 | days = 3650 4 | distinguished_name = req_distinguished_name 5 | attributes = req_attributes 6 | prompt = no 7 | output_password = password 8 | 9 | [ req_distinguished_name ] 10 | C = US 11 | ST = CA 12 | L = Oakland 13 | O = request 14 | OU = request@localhost 15 | CN = TestClient 16 | emailAddress = do.not@email.me 17 | 18 | [ req_attributes ] 19 | challengePassword = password challenge 20 | 21 | -------------------------------------------------------------------------------- /tests/ssl/ca/localhost.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 1024 3 | days = 3650 4 | distinguished_name = req_distinguished_name 5 | attributes = req_attributes 6 | prompt = no 7 | output_password = password 8 | 9 | [ req_distinguished_name ] 10 | C = US 11 | ST = CA 12 | L = Oakland 13 | O = request 14 | OU = request@localhost 15 | CN = localhost 16 | emailAddress = do.not@email.me 17 | 18 | [ req_attributes ] 19 | challengePassword = password challenge 20 | 21 | -------------------------------------------------------------------------------- /tests/ssl/ca/ca.cnf: -------------------------------------------------------------------------------- 1 | [ req ] 2 | default_bits = 1024 3 | days = 3650 4 | distinguished_name = req_distinguished_name 5 | attributes = req_attributes 6 | prompt = no 7 | output_password = password 8 | 9 | [ req_distinguished_name ] 10 | C = US 11 | ST = CA 12 | L = Oakland 13 | O = request 14 | OU = request Certificate Authority 15 | CN = requestCA 16 | emailAddress = mikeal@mikealrogers.com 17 | 18 | [ req_attributes ] 19 | challengePassword = password challenge 20 | 21 | -------------------------------------------------------------------------------- /tests/ssl/ca/gen-localhost.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Adapted from: 4 | # http://nodejs.org/api/tls.html 5 | # https://github.com/joyent/node/blob/master/test/fixtures/keys/Makefile 6 | 7 | # Create a private key 8 | openssl genrsa -out localhost.key 2048 9 | 10 | # Create a certificate signing request 11 | openssl req -new -sha256 -key localhost.key -out localhost.csr -config localhost.cnf -days 1095 12 | 13 | # Use the CSR and the CA key (previously generated) to create a certificate 14 | openssl x509 -req \ 15 | -in localhost.csr \ 16 | -CA ca.crt \ 17 | -CAkey ca.key \ 18 | -set_serial 0x`cat ca.srl` \ 19 | -passin 'pass:password' \ 20 | -out localhost.crt \ 21 | -days 1095 22 | -------------------------------------------------------------------------------- /tests/ssl/ca/gen-client.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # Adapted from: 4 | # http://nodejs.org/api/tls.html 5 | # https://github.com/joyent/node/blob/master/test/fixtures/keys/Makefile 6 | 7 | # Create a private key 8 | openssl genrsa -out client.key 2048 9 | 10 | # Create a certificate signing request 11 | openssl req -new -sha256 -key client.key -out client.csr -config client.cnf -days 1095 12 | 13 | # Use the CSR and the CA key (previously generated) to create a certificate 14 | openssl x509 -req \ 15 | -in client.csr \ 16 | -CA ca.crt \ 17 | -CAkey ca.key \ 18 | -set_serial 0x`cat ca.srl` \ 19 | -passin 'pass:password' \ 20 | -out client.crt \ 21 | -days 1095 22 | 23 | # Encrypt with password 24 | openssl rsa -aes128 -in client.key -out client-enc.key -passout 'pass:password' 25 | -------------------------------------------------------------------------------- /tests/ssl/ca/ca.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICBjCCAW8CAQAwgaIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTEQMA4GA1UE 3 | BxMHT2FrbGFuZDEQMA4GA1UEChMHcmVxdWVzdDEmMCQGA1UECxMdcmVxdWVzdCBD 4 | ZXJ0aWZpY2F0ZSBBdXRob3JpdHkxEjAQBgNVBAMTCXJlcXVlc3RDQTEmMCQGCSqG 5 | SIb3DQEJARYXbWlrZWFsQG1pa2VhbHJvZ2Vycy5jb20wgZ8wDQYJKoZIhvcNAQEB 6 | BQADgY0AMIGJAoGBALu32lBQArjlclhMjo2sXSfcbYdmx836s9hJXPMvxK5XJvW0 7 | deiz69s+bc0ojgTxfXJUBdTwKMnlKpeengMDKkCFqx6GjzVxuGTstdSEXPvw68Br 8 | 44P7FMAQCc1Dy0ZSvhUbKmUSN8PGwFW6pXaPwY81N+2v52s4IWWp8CAysGfjAgMB 9 | AAGgIzAhBgkqhkiG9w0BCQcxFBMScGFzc3dvcmQgY2hhbGxlbmdlMA0GCSqGSIb3 10 | DQEBBQUAA4GBAGJO7grHeVHXetjHEK8urIxdnvfB2qeZeObz4GPKIkqUurjr0rfj 11 | bA3EK1kDMR5aeQWR8RunixdM16Q6Ry0lEdLVWkdSwRN9dmirIHT9cypqnD/FYOia 12 | SdezZ0lUzXgmJIwRYRwB1KSMMocIf52ll/xC2bEGg7/ZAEuAyAgcZV3X 13 | -----END CERTIFICATE REQUEST----- 14 | -------------------------------------------------------------------------------- /tests/test-form-data-error.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , server = require('./server') 5 | , tape = require('tape') 6 | 7 | var s = server.createServer() 8 | 9 | tape('setup', function(t) { 10 | s.listen(s.port, function() { 11 | t.end() 12 | }) 13 | }) 14 | 15 | tape('re-emit formData errors', function(t) { 16 | s.on('/', function(req, res) { 17 | res.writeHead(400) 18 | res.end() 19 | t.fail('The form-data error did not abort the request.') 20 | }) 21 | 22 | request.post(s.url, function (err, res, body) { 23 | t.equal(err.message, 'form-data: Arrays are not supported.') 24 | setTimeout(function() { 25 | t.end() 26 | }, 10) 27 | }).form().append('field', ['value1', 'value2']) 28 | }) 29 | 30 | tape('cleanup', function(t) { 31 | s.close(function() { 32 | t.end() 33 | }) 34 | }) 35 | -------------------------------------------------------------------------------- /tests/browser/ssl/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICKTCCAZICCQDB/6lRlsirjzANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJB 3 | VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 4 | cyBQdHkgTHRkMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTUwMTA3MTcwODM2WhcN 5 | MjUwMTA0MTcwODM2WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 6 | ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDEwls 7 | b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAMba6FQ1VkgW8vWa 8 | FBxV1VdLhQ5HP0eKZ/CyEGG4r89CzfzC0+V3bnFWGBGF2kSJlVjc7eVSSVio383A 9 | inq3i+86Mavfy18BwcP4I0NqUSvvcV9yduBLUySklJhOlhhHeFUlycQyxuODbrG9 10 | QOd411c4eccsbDHq5vSnS7AJh6tVAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEAI0H3 11 | bJIUQgnSGyrSHmjoKm66QLBPvQ1Zd7Zxjlg1uFv6glPOhdkeTQx9XQPT/WDG3qmJ 12 | BdHvQLDtPS9P8vRaiQW1OCP7dQJkVYCxyFbSQiieuzwBAEGtZcLdZbvcp3PKRGbx 13 | sIrkzyYdAXE1EZ5z7yLVcpWwTKnBnuRz2D0XOk4= 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /tests/browser/ssl/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICKTCCAZICCQDl9xx8ZXLMPTANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJB 3 | VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 4 | cyBQdHkgTHRkMRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMTUwMTA3MTcwOTQ4WhcN 5 | MjUwMTA0MTcwOTQ4WjBZMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0 6 | ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRIwEAYDVQQDEwls 7 | b2NhbGhvc3QwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKWxvvLNi8AcT0wI 8 | sf+LoWAvtoIV29ypI6j1JRqmsPO433JP/ijLhJLFc6RKXpKs6pd4am82vzk8Bxs3 9 | VtUXJ0yKh3KMevT7L4X1hw+QxvYAZD6Kl/kaNvKFTuAgcaeSnmnWGjQYLF/i20w7 10 | 7dpeXDmnNMCKwdg+kLeJdPtW0d+vAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEADL6l 11 | Z2mDKj4kPyGD58uBGeBHjmcDA2oJcnsHhOiC1u1NuCwQW4RDWs6Ili0GhuHYHP0B 12 | JDcPw6ZSa1Gx3ZaUJ5yM/+YHpbLev34CjmiwQeG36DF2rAxfoIQk/wI4iWmu1+ed 13 | 5Wwc0cZAb10uy0ihmMK98yhVQPmkBOEyw2O1xJw= 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /tests/ssl/ca/localhost.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var https = require('https') 5 | var options = { key: fs.readFileSync('./localhost.key') 6 | , cert: fs.readFileSync('./localhost.crt') } 7 | 8 | var server = https.createServer(options, function (req, res) { 9 | res.writeHead(200) 10 | res.end() 11 | server.close() 12 | }) 13 | server.listen(1337) 14 | 15 | var ca = fs.readFileSync('./ca.crt') 16 | var agent = new https.Agent({ host: 'localhost', port: 1337, ca: ca }) 17 | 18 | https.request({ host: 'localhost' 19 | , method: 'HEAD' 20 | , port: 1337 21 | , agent: agent 22 | , ca: [ ca ] 23 | , path: '/' }, function (res) { 24 | if (res.socket.authorized) { 25 | console.log('node test: OK') 26 | } else { 27 | throw new Error(res.socket.authorizationError) 28 | } 29 | }).end() 30 | -------------------------------------------------------------------------------- /tests/ssl/test.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICQzCCAawCCQCO/XWtRFck1jANBgkqhkiG9w0BAQUFADBmMQswCQYDVQQGEwJU 3 | SDEQMA4GA1UECBMHQmFuZ2tvazEOMAwGA1UEBxMFU2lsb20xGzAZBgNVBAoTElRo 4 | ZSBSZXF1ZXN0IE1vZHVsZTEYMBYGA1UEAxMPcmVxdWVzdC5leGFtcGxlMB4XDTEx 5 | MTIwMzAyMjkyM1oXDTIxMTEzMDAyMjkyM1owZjELMAkGA1UEBhMCVEgxEDAOBgNV 6 | BAgTB0Jhbmdrb2sxDjAMBgNVBAcTBVNpbG9tMRswGQYDVQQKExJUaGUgUmVxdWVz 7 | dCBNb2R1bGUxGDAWBgNVBAMTD3JlcXVlc3QuZXhhbXBsZTCBnzANBgkqhkiG9w0B 8 | AQEFAAOBjQAwgYkCgYEAwmctddZqlA48+NXs0yOy92DijcQV1jf87zMiYAIlNUto 9 | wghVbTWgJU5r0pdKrD16AptnWJTzKanhItEX8XCCPgsNkq1afgTtJP7rNkwu3xcj 10 | eIMkhJg/ay4ZnkbnhYdsii5VTU5prix6AqWRAhbkBgoA+iVyHyof8wvZyKBoFTMC 11 | AwEAATANBgkqhkiG9w0BAQUFAAOBgQB6BybMJbpeiABgihDfEVBcAjDoQ8gUMgwV 12 | l4NulugfKTDmArqnR9aPd4ET5jX5dkMP4bwCHYsvrcYDeWEQy7x5WWuylOdKhua4 13 | L4cEi2uDCjqEErIG3cc1MCOk6Cl6Ld6tkIzQSf953qfdEACRytOeUqLNQcrXrqeE 14 | c7U8F6MWLQ== 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /tests/ssl/test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQDCZy111mqUDjz41ezTI7L3YOKNxBXWN/zvMyJgAiU1S2jCCFVt 3 | NaAlTmvSl0qsPXoCm2dYlPMpqeEi0RfxcII+Cw2SrVp+BO0k/us2TC7fFyN4gySE 4 | mD9rLhmeRueFh2yKLlVNTmmuLHoCpZECFuQGCgD6JXIfKh/zC9nIoGgVMwIDAQAB 5 | AoGBALXFwfUf8vHTSmGlrdZS2AGFPvEtuvldyoxi9K5u8xmdFCvxnOcLsF2RsTHt 6 | Mu5QYWhUpNJoG+IGLTPf7RJdj/kNtEs7xXqWy4jR36kt5z5MJzqiK+QIgiO9UFWZ 7 | fjUb6oeDnTIJA9YFBdYi97MDuL89iU/UK3LkJN3hd4rciSbpAkEA+MCkowF5kSFb 8 | rkOTBYBXZfiAG78itDXN6DXmqb9XYY+YBh3BiQM28oxCeQYyFy6pk/nstnd4TXk6 9 | V/ryA2g5NwJBAMgRKTY9KvxJWbESeMEFe2iBIV0c26/72Amgi7ZKUCLukLfD4tLF 10 | +WSZdmTbbqI1079YtwaiOVfiLm45Q/3B0eUCQAaQ/0eWSGE+Yi8tdXoVszjr4GXb 11 | G81qBi91DMu6U1It+jNfIba+MPsiHLcZJMVb4/oWBNukN7bD1nhwFWdlnu0CQQCf 12 | Is9WHkdvz2RxbZDxb8verz/7kXXJQJhx5+rZf7jIYFxqX3yvTNv3wf2jcctJaWlZ 13 | fVZwB193YSivcgt778xlAkEAprYUz3jczjF5r2hrgbizPzPDR94tM5BTO3ki2v3w 14 | kbf+j2g7FNAx6kZiVN8XwfLc8xEeUGiPKwtq3ddPDFh17w== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/test-event-forwarding.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var server = require('./server') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var s = server.createServer() 8 | 9 | tape('setup', function(t) { 10 | s.listen(s.port, function() { 11 | s.on('/', function(req, res) { 12 | res.writeHead(200, { 'content-type': 'text/plain' }) 13 | res.write('waited') 14 | res.end() 15 | }) 16 | t.end() 17 | }) 18 | }) 19 | 20 | tape('should emit socket event', function(t) { 21 | t.plan(4) 22 | 23 | var req = request(s.url, function(err, res, body) { 24 | t.equal(err, null) 25 | t.equal(res.statusCode, 200) 26 | t.equal(body, 'waited') 27 | }) 28 | 29 | req.on('socket', function(socket) { 30 | var requestSocket = req.req.socket 31 | t.equal(requestSocket, socket) 32 | }) 33 | }) 34 | 35 | tape('cleanup', function(t) { 36 | s.close(function() { 37 | t.end() 38 | }) 39 | }) 40 | -------------------------------------------------------------------------------- /tests/browser/ssl/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQClsb7yzYvAHE9MCLH/i6FgL7aCFdvcqSOo9SUaprDzuN9yT/4o 3 | y4SSxXOkSl6SrOqXeGpvNr85PAcbN1bVFydMiodyjHr0+y+F9YcPkMb2AGQ+ipf5 4 | GjbyhU7gIHGnkp5p1ho0GCxf4ttMO+3aXlw5pzTAisHYPpC3iXT7VtHfrwIDAQAB 5 | AoGBAJa5edmk4NuA5SFlR4YOnl3BCWSMPdQciDPJzFbSC2WpZpm16p1xhMd+lhN9 6 | E0qZwUzIXQmN46VM1aoMTRDKXxPqujUIvhn7kxMLmD8lajHzFUIhgnp1XQCfxIIV 7 | sCcnIoP+cbnzP+AegAEPjds/0ngI3YM28UeooqZAmZCHQ0cBAkEAz0go7tCxXqED 8 | +cY+P2axAKuGR+6srx08g5kONTpUx8jXr4su02F376dxhPB9UXWOJkYiGEBwKEds 9 | OnUSNTF/RQJBAMyjUzjb/u6sZqTcHd3owes3UsCC+kfSb814qdG3Z9qYX9p55ESu 10 | hA7Sbjq0WdTHGZdgEexwpfLtTRS8x5ldiGMCQFC3GLlmKqtep92rhLHLm0FXiYKZ 11 | PkUybU4RW6b+f+UMIHELEcDeQ4Xe/iV2QFZoIGJnDP/El+gXZ92bmOt9ysECQD/i 12 | zVx28gO5NuJJBdn9jGzOfLs1KMW7YMQY44thYr7Pyzz9yNHYWcn20Arruw++iLLF 13 | f1L9aBGLHAFZXkb2+FkCQA5/3Z3SgiZrRYX/bWcNe6N65+nGfJv8ZBVsX13pKOxR 14 | 8JzJLyEmx67IOGZvVgfVABrCHJvTrKlUO3x3mDoEPzI= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/ssl/ca/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var https = require('https') 5 | var options = { key: fs.readFileSync('./server.key') 6 | , cert: fs.readFileSync('./server.crt') } 7 | 8 | var server = https.createServer(options, function (req, res) { 9 | res.writeHead(200) 10 | res.end() 11 | server.close() 12 | }) 13 | server.listen(1337) 14 | 15 | var ca = fs.readFileSync('./ca.crt') 16 | var agent = new https.Agent({ host: 'localhost', port: 1337, ca: ca }) 17 | 18 | https.request({ host: 'localhost' 19 | , method: 'HEAD' 20 | , port: 1337 21 | , headers: { host: 'testing.request.mikealrogers.com' } 22 | , agent: agent 23 | , ca: [ ca ] 24 | , path: '/' }, function (res) { 25 | if (res.socket.authorized) { 26 | console.log('node test: OK') 27 | } else { 28 | throw new Error(res.socket.authorizationError) 29 | } 30 | }).end() 31 | -------------------------------------------------------------------------------- /tests/ssl/ca/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICejCCAeMCCQCt9iAWqkDJwzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC 3 | VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMRAwDgYDVQQKEwdyZXF1 4 | ZXN0MSYwJAYDVQQLEx1yZXF1ZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTESMBAG 5 | A1UEAxMJcmVxdWVzdENBMSYwJAYJKoZIhvcNAQkBFhdtaWtlYWxAbWlrZWFscm9n 6 | ZXJzLmNvbTAeFw0xMjAzMDEyMjUwNTZaFw0yMjAyMjcyMjUwNTZaMIGjMQswCQYD 7 | VQQGEwJVUzELMAkGA1UECBMCQ0ExEDAOBgNVBAcTB09ha2xhbmQxEDAOBgNVBAoT 8 | B3JlcXVlc3QxEDAOBgNVBAsTB3Rlc3RpbmcxKTAnBgNVBAMTIHRlc3RpbmcucmVx 9 | dWVzdC5taWtlYWxyb2dlcnMuY29tMSYwJAYJKoZIhvcNAQkBFhdtaWtlYWxAbWlr 10 | ZWFscm9nZXJzLmNvbTBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQDgVl0jMumvOpmM 11 | 20W5v9yhGgZj8hPhEQF/N7yCBVBn/rWGYm70IHC8T/pR5c0LkWc5gdnCJEvKWQjh 12 | DBKxZD8FAgMBAAEwDQYJKoZIhvcNAQEFBQADgYEABShRkNgFbgs4vUWW9R9deNJj 13 | 7HJoiTmvkmoOC7QzcYkjdgHbOxsSq3rBnwxsVjY9PAtPwBn0GRspOeG7KzKRgySB 14 | kb22LyrCFKbEOfKO/+CJc80ioK9zEPVjGsFMyAB+ftYRqM+s/4cQlTg/m89l01wC 15 | yapjN3RxZbInGhWR+jA= 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /tests/ssl/npm-ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIChzCCAfACCQDauvz/KHp8ejANBgkqhkiG9w0BAQUFADCBhzELMAkGA1UEBhMC 3 | VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMQwwCgYDVQQKEwNucG0x 4 | IjAgBgNVBAsTGW5wbSBDZXJ0aWZpY2F0ZSBBdXRob3JpdHkxDjAMBgNVBAMTBW5w 5 | bUNBMRcwFQYJKoZIhvcNAQkBFghpQGl6cy5tZTAeFw0xMTA5MDUwMTQ3MTdaFw0y 6 | MTA5MDIwMTQ3MTdaMIGHMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEDAOBgNV 7 | BAcTB09ha2xhbmQxDDAKBgNVBAoTA25wbTEiMCAGA1UECxMZbnBtIENlcnRpZmlj 8 | YXRlIEF1dGhvcml0eTEOMAwGA1UEAxMFbnBtQ0ExFzAVBgkqhkiG9w0BCQEWCGlA 9 | aXpzLm1lMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDLI4tIqPpRW+ACw9GE 10 | OgBlJZwK5f8nnKCLK629Pv5yJpQKs3DENExAyOgDcyaF0HD0zk8zTp+ZsLaNdKOz 11 | Gn2U181KGprGKAXP6DU6ByOJDWmTlY6+Ad1laYT0m64fERSpHw/hjD3D+iX4aMOl 12 | y0HdbT5m1ZGh6SJz3ZqxavhHLQIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAC4ySDbC 13 | l7W1WpLmtLGEQ/yuMLUf6Jy/vr+CRp4h+UzL+IQpCv8FfxsYE7dhf/bmWTEupBkv 14 | yNL18lipt2jSvR3v6oAHAReotvdjqhxddpe5Holns6EQd1/xEZ7sB1YhQKJtvUrl 15 | ZNufy1Jf1r0ldEGeA+0ISck7s+xSh9rQD2Op 16 | -----END CERTIFICATE----- 17 | -------------------------------------------------------------------------------- /tests/browser/test.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (!Function.prototype.bind) { 4 | // This is because of the fact that phantom.js does not have Function.bind. 5 | // This is a bug in phantom.js. 6 | // More info: https://github.com/ariya/phantomjs/issues/10522 7 | /*eslint no-extend-native:0*/ 8 | Function.prototype.bind = require('function-bind') 9 | } 10 | 11 | 12 | var tape = require('tape') 13 | , request = require('../../index') 14 | 15 | tape('returns on error', function(t) { 16 | t.plan(1) 17 | request({ 18 | uri: 'https://stupid.nonexistent.path:port123/\\<-great-idea', 19 | withCredentials: false 20 | }, function (error, response) { 21 | t.equal(response.statusCode, 0) 22 | t.end() 23 | }) 24 | }) 25 | 26 | tape('succeeds on valid URLs (with https and CORS)', function(t) { 27 | t.plan(1) 28 | request({ 29 | uri: 'https://localhost:6767', 30 | withCredentials: false 31 | }, function (error, response) { 32 | t.equal(response.statusCode, 200) 33 | t.end() 34 | }) 35 | }) 36 | -------------------------------------------------------------------------------- /tests/ssl/ca/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,C8B5887048377F02 4 | 5 | nyD5ZH0Wup2uWsDvurq5mKDaDrf8lvNn9w0SH/ZkVnfR1/bkwqrFriqJWvZNUG+q 6 | nS0iBYczsWLJnbub9a1zLOTENWUKVD5uqbC3aGHhnoUTNSa27DONgP8gHOn6JgR+ 7 | GAKo01HCSTiVT4LjkwN337QKHnMP2fTzg+IoC/CigvMcq09hRLwU1/guq0GJKGwH 8 | gTxYNuYmQC4Tjh8vdS4liF+Ve/P3qPR2CehZrIOkDT8PHJBGQJRo4xGUIB7Tpk38 9 | VCk+UZ0JCS2coY8VkY/9tqFJp/ZnnQQVmaNbdRqg7ECKL+bXnNo7yjzmazPZmPe3 10 | /ShbE0+CTt7LrjCaQAxWbeDzqfo1lQfgN1LulTm8MCXpQaJpv7v1VhIhQ7afjMYb 11 | 4thW/ypHPiYS2YJCAkAVlua9Oxzzh1qJoh8Df19iHtpd79Q77X/qf+1JvITlMu0U 12 | gi7yEatmQcmYNws1mtTC1q2DXrO90c+NZ0LK/Alse6NRL/xiUdjug2iHeTf/idOR 13 | Gg/5dSZbnnlj1E5zjSMDkzg6EHAFmHV4jYGSAFLEQgp4V3ZhMVoWZrvvSHgKV/Qh 14 | FqrAK4INr1G2+/QTd09AIRzfy3/j6yD4A9iNaOsEf9Ua7Qh6RcALRCAZTWR5QtEf 15 | dX+iSNJ4E85qXs0PqwkMDkoaxIJ+tmIRJY7y8oeylV8cfGAi8Soubt/i3SlR8IHC 16 | uDMas/2OnwafK3N7ODeE1i7r7wkzQkSHaEz0TrF8XRnP25jAICCSLiMdAAjKfxVb 17 | EvzsFSuAy3Jt6bU3hSLY9o4YVYKE+68ITMv9yNjvTsEiW+T+IbN34w== 18 | -----END RSA PRIVATE KEY----- 19 | -------------------------------------------------------------------------------- /tests/test-follow-all-303.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var server = http.createServer(function (req, res) { 8 | if (req.method === 'POST') { 9 | res.setHeader('location', req.url) 10 | res.statusCode = 303 11 | res.end('try again') 12 | } else { 13 | res.end('ok') 14 | } 15 | }) 16 | 17 | tape('setup', function(t) { 18 | server.listen(6767, function() { 19 | t.end() 20 | }) 21 | }) 22 | 23 | tape('followAllRedirects with 303', function(t) { 24 | var redirects = 0 25 | 26 | request.post({ 27 | url: 'http://localhost:6767/foo', 28 | followAllRedirects: true, 29 | form: { foo: 'bar' } 30 | }, function (err, res, body) { 31 | t.equal(err, null) 32 | t.equal(body, 'ok') 33 | t.equal(redirects, 1) 34 | t.end() 35 | }).on('redirect', function() { 36 | redirects++ 37 | }) 38 | }) 39 | 40 | tape('cleanup', function(t) { 41 | server.close(function() { 42 | t.end() 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /tests/ssl/ca/ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICvTCCAiYCCQDn+P/MSbDsWjANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC 3 | VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMRAwDgYDVQQKEwdyZXF1 4 | ZXN0MSYwJAYDVQQLEx1yZXF1ZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTESMBAG 5 | A1UEAxMJcmVxdWVzdENBMSYwJAYJKoZIhvcNAQkBFhdtaWtlYWxAbWlrZWFscm9n 6 | ZXJzLmNvbTAeFw0xMjAzMDEyMjUwNTZaFw0yMjAyMjcyMjUwNTZaMIGiMQswCQYD 7 | VQQGEwJVUzELMAkGA1UECBMCQ0ExEDAOBgNVBAcTB09ha2xhbmQxEDAOBgNVBAoT 8 | B3JlcXVlc3QxJjAkBgNVBAsTHXJlcXVlc3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5 9 | MRIwEAYDVQQDEwlyZXF1ZXN0Q0ExJjAkBgkqhkiG9w0BCQEWF21pa2VhbEBtaWtl 10 | YWxyb2dlcnMuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC7t9pQUAK4 11 | 5XJYTI6NrF0n3G2HZsfN+rPYSVzzL8SuVyb1tHXos+vbPm3NKI4E8X1yVAXU8CjJ 12 | 5SqXnp4DAypAhaseho81cbhk7LXUhFz78OvAa+OD+xTAEAnNQ8tGUr4VGyplEjfD 13 | xsBVuqV2j8GPNTftr+drOCFlqfAgMrBn4wIDAQABMA0GCSqGSIb3DQEBBQUAA4GB 14 | ADVdTlVAL45R+PACNS7Gs4o81CwSclukBu4FJbxrkd4xGQmurgfRrYYKjtqiopQm 15 | D7ysRamS3HMN9/VKq2T7r3z1PMHPAy7zM4uoXbbaTKwlnX4j/8pGPn8Ca3qHXYlo 16 | 88L/OOPc6Di7i7qckS3HFbXQCTiULtxWmy97oEuTwrAj 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /tests/test-toJSON.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , http = require('http') 5 | , tape = require('tape') 6 | 7 | var s = http.createServer(function (req, resp) { 8 | resp.statusCode = 200 9 | resp.end('asdf') 10 | }) 11 | 12 | tape('setup', function(t) { 13 | s.listen(6767, function() { 14 | t.end() 15 | }) 16 | }) 17 | 18 | tape('request().toJSON()', function(t) { 19 | var r = request({ 20 | url: 'http://localhost:6767', 21 | headers: { foo: 'bar' } 22 | }, function(err, res) { 23 | var json_r = JSON.parse(JSON.stringify(r)) 24 | , json_res = JSON.parse(JSON.stringify(res)) 25 | 26 | t.equal(err, null) 27 | 28 | t.equal(json_r.uri.href, r.uri.href) 29 | t.equal(json_r.method, r.method) 30 | t.equal(json_r.headers.foo, r.headers.foo) 31 | 32 | t.equal(json_res.statusCode, res.statusCode) 33 | t.equal(json_res.body, res.body) 34 | t.equal(json_res.headers.date, res.headers.date) 35 | 36 | t.end() 37 | }) 38 | }) 39 | 40 | tape('cleanup', function(t) { 41 | s.close(function() { 42 | t.end() 43 | }) 44 | }) 45 | -------------------------------------------------------------------------------- /tests/ssl/ca/client.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC+DCCAeACAQAwgY8xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEQMA4GA1UE 3 | BwwHT2FrbGFuZDEQMA4GA1UECgwHcmVxdWVzdDEaMBgGA1UECwwRcmVxdWVzdEBs 4 | b2NhbGhvc3QxEzARBgNVBAMMClRlc3RDbGllbnQxHjAcBgkqhkiG9w0BCQEWD2Rv 5 | Lm5vdEBlbWFpbC5tZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANhK 6 | SviWyzGiXjLRD/WaP1LUUBnupnH2mYrpyoEqaF1Qun1G6JxhRQbbUdsA/oQBq3ON 7 | HfcoCAiUHWOkB9MS35GrJGsA512NiMFadVWOMPJK8AHAEAJ7ACeGkAqq4Rm6koBK 8 | EBm27wbdbZbtdO1kyTJb//oQu7WxmiMGhn0zVMfsQ24bu+MtrsNUd3qqq1nJUUPw 9 | WMA3/J6t8mbnu30r2HoeZl/C/UhPc8K3K0rATK1rExMMEm5d6frwuiazOUReXYqc 10 | edI2gRGmg3Q3FAiP7izIN/rnFMeQnkS/ZlwZY6oxk0ps76bElhfmvi6TwZy6C0Pk 11 | jeshVfyxk0qtAXOkWcECAwEAAaAjMCEGCSqGSIb3DQEJBzEUDBJwYXNzd29yZCBj 12 | aGFsbGVuZ2UwDQYJKoZIhvcNAQELBQADggEBAADv7KZq1ZxniXFe2SgWbvsvmsLA 13 | 5C/8SLH7MB9EQkDGQmyG5nsX98BQtNUR+rXvvXd/1piFBfZD6K/iy26N0ltDxt3H 14 | JLKnWSbJctEKR+A9Nff1NPQsVlWSXEnXyRHqv8+pJlV0o1yl3TtSmTlL6fgVe0Ii 15 | 8D8w9QDTX3VT6M53BQtVaXJCpN6B943RvOeeKhOa/zyq0QU2a8+Tqm05qXHGQPCx 16 | ZkcGH861tuQuR/UyPEJLpSpMdVUsstWLuOlpontVZO1pa4kRaWzKONzfDrfX+g58 17 | tLFyrEl2vRni2tRdQHEXAPs5zvbGQ5wHouF8kp5cvQDmH4HYZAdV2ZSyOlQ= 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /tests/ssl/ca/localhost.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIIC9zCCAd8CAQAwgY4xCzAJBgNVBAYTAlVTMQswCQYDVQQIDAJDQTEQMA4GA1UE 3 | BwwHT2FrbGFuZDEQMA4GA1UECgwHcmVxdWVzdDEaMBgGA1UECwwRcmVxdWVzdEBs 4 | b2NhbGhvc3QxEjAQBgNVBAMMCWxvY2FsaG9zdDEeMBwGCSqGSIb3DQEJARYPZG8u 5 | bm90QGVtYWlsLm1lMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArl35 6 | 98SpzKRa/87o4u+x2/fLaH+MrZGUThJKCqEKhLYnhfTwHosYc6z4hww60ZXb10vL 7 | /Ccwp2pVz8KoZ3QoHrORURFa3QeZyhPcBN4hyzyTI76L74tAH7lg1xJuZdkTiF78 8 | 6gxYsnAsXNsLNIcJkZ262cA7ymV1XUBYwpidykNCPFU4vYmLayAkVrwl+XZzpvOl 9 | gnEetb5t1aGrtCMiozhO1xGSZbg0YyZ/aw2dMLmm8jr9sW9BJKBhDlmNHrK28XF7 10 | KjRzDJblrNN2CRHNcdqfabYOHhuRwu71F5FW4no/p8BOtRAP3WYzzkFvxDOf4ves 11 | 4pK4ByaDHzCtMfKN5QIDAQABoCMwIQYJKoZIhvcNAQkHMRQMEnBhc3N3b3JkIGNo 12 | YWxsZW5nZTANBgkqhkiG9w0BAQsFAAOCAQEAZhCYjPuSzKGqXGR+OcbCU+m8VmHA 13 | FpBp04VEYxtStagi+m2m7JUDOsTm+NdMj7lBTMEX5eK6sLadeZjkwS7bZNSiq54b 14 | 2g5Yqom29LTQCKACBra+9iH3Y4CUIO0zxmki9QMlMBt5gU9DJEr4m9qk216s1hn+ 15 | FNZ5ytU6756y3eYnGOvJSUfhTKj+AWzljgRtgOsaEhnP/299LTjXrsLirO/5bbm8 16 | f7qes5FtNWBYlRYx3nejouiquVZVmPYSi663dESLp/R35qV0Bg1Tam+9zGGysTuY 17 | A8IYVUSqik3cpj6Kfu6UBv9KACWeKznjFrvz4dKrDho4YS/K4Zi3cqbEfA== 18 | -----END CERTIFICATE REQUEST----- 19 | -------------------------------------------------------------------------------- /tests/ssl/ca/client.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLjCCApcCCQCt9iAWqkDJwzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC 3 | VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMRAwDgYDVQQKEwdyZXF1 4 | ZXN0MSYwJAYDVQQLEx1yZXF1ZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTESMBAG 5 | A1UEAxMJcmVxdWVzdENBMSYwJAYJKoZIhvcNAQkBFhdtaWtlYWxAbWlrZWFscm9n 6 | ZXJzLmNvbTAeFw0xNTA4MDMxNjM5MDJaFw0xODA4MDIxNjM5MDJaMIGPMQswCQYD 7 | VQQGEwJVUzELMAkGA1UECAwCQ0ExEDAOBgNVBAcMB09ha2xhbmQxEDAOBgNVBAoM 8 | B3JlcXVlc3QxGjAYBgNVBAsMEXJlcXVlc3RAbG9jYWxob3N0MRMwEQYDVQQDDApU 9 | ZXN0Q2xpZW50MR4wHAYJKoZIhvcNAQkBFg9kby5ub3RAZW1haWwubWUwggEiMA0G 10 | CSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDYSkr4lssxol4y0Q/1mj9S1FAZ7qZx 11 | 9pmK6cqBKmhdULp9RuicYUUG21HbAP6EAatzjR33KAgIlB1jpAfTEt+RqyRrAOdd 12 | jYjBWnVVjjDySvABwBACewAnhpAKquEZupKAShAZtu8G3W2W7XTtZMkyW//6ELu1 13 | sZojBoZ9M1TH7ENuG7vjLa7DVHd6qqtZyVFD8FjAN/yerfJm57t9K9h6HmZfwv1I 14 | T3PCtytKwEytaxMTDBJuXen68LomszlEXl2KnHnSNoERpoN0NxQIj+4syDf65xTH 15 | kJ5Ev2ZcGWOqMZNKbO+mxJYX5r4uk8GcugtD5I3rIVX8sZNKrQFzpFnBAgMBAAEw 16 | DQYJKoZIhvcNAQEFBQADgYEAKSut5ZyFcEDl4SSUKsnEXV1Z4sfWk1WTnZP8D8qX 17 | L/Mge0Gx36my6OtdJ0JFA1mO9gR3KyS9CDh3OgwWCg9HtoArriqLhBHE0oy7NYa2 18 | uRFraeLO5fGKk6FKePb3DRF8i9tFMQBhoVAZhX8f6hw+g3Xt5fDAHMumG2qMeuMQ 19 | l4I= 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /tests/ssl/ca/localhost.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLTCCApYCCQCt9iAWqkDJwzANBgkqhkiG9w0BAQUFADCBojELMAkGA1UEBhMC 3 | VVMxCzAJBgNVBAgTAkNBMRAwDgYDVQQHEwdPYWtsYW5kMRAwDgYDVQQKEwdyZXF1 4 | ZXN0MSYwJAYDVQQLEx1yZXF1ZXN0IENlcnRpZmljYXRlIEF1dGhvcml0eTESMBAG 5 | A1UEAxMJcmVxdWVzdENBMSYwJAYJKoZIhvcNAQkBFhdtaWtlYWxAbWlrZWFscm9n 6 | ZXJzLmNvbTAeFw0xNTA4MDMxNjM5NThaFw0xODA4MDIxNjM5NThaMIGOMQswCQYD 7 | VQQGEwJVUzELMAkGA1UECAwCQ0ExEDAOBgNVBAcMB09ha2xhbmQxEDAOBgNVBAoM 8 | B3JlcXVlc3QxGjAYBgNVBAsMEXJlcXVlc3RAbG9jYWxob3N0MRIwEAYDVQQDDAls 9 | b2NhbGhvc3QxHjAcBgkqhkiG9w0BCQEWD2RvLm5vdEBlbWFpbC5tZTCCASIwDQYJ 10 | KoZIhvcNAQEBBQADggEPADCCAQoCggEBAK5d+ffEqcykWv/O6OLvsdv3y2h/jK2R 11 | lE4SSgqhCoS2J4X08B6LGHOs+IcMOtGV29dLy/wnMKdqVc/CqGd0KB6zkVERWt0H 12 | mcoT3ATeIcs8kyO+i++LQB+5YNcSbmXZE4he/OoMWLJwLFzbCzSHCZGdutnAO8pl 13 | dV1AWMKYncpDQjxVOL2Ji2sgJFa8Jfl2c6bzpYJxHrW+bdWhq7QjIqM4TtcRkmW4 14 | NGMmf2sNnTC5pvI6/bFvQSSgYQ5ZjR6ytvFxeyo0cwyW5azTdgkRzXHan2m2Dh4b 15 | kcLu9ReRVuJ6P6fATrUQD91mM85Bb8Qzn+L3rOKSuAcmgx8wrTHyjeUCAwEAATAN 16 | BgkqhkiG9w0BAQUFAAOBgQAFhiBnCVsgk3Gn8kqKoAMqEd4Ckk3w6Fuj+C468lDM 17 | HGrX6e1pPO8UwVNUye1U2nRkVmO92IPsENrnLvIoqbtXR4w6T0DWg+ilRgJsV/Ra 18 | hVZBJxYthAtvyfNBnwd9FV3jC3waEsKRcnLDDkkBOfYtMeUzHuCBAwf5c7vuNC+N 19 | xQ== 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /tests/test-option-reuse.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , http = require('http') 5 | , tape = require('tape') 6 | 7 | var methodsSeen = { 8 | head: 0, 9 | get: 0 10 | } 11 | 12 | var s = http.createServer(function(req, res) { 13 | res.statusCode = 200 14 | res.end('ok') 15 | 16 | methodsSeen[req.method.toLowerCase()]++ 17 | }) 18 | 19 | tape('setup', function(t) { 20 | s.listen(6767, function() { 21 | t.end() 22 | }) 23 | }) 24 | 25 | tape('options object is not mutated', function(t) { 26 | var url = 'http://localhost:6767' 27 | var options = { url: url } 28 | 29 | request.head(options, function(err, resp, body) { 30 | t.equal(err, null) 31 | t.equal(body, '') 32 | t.equal(Object.keys(options).length, 1) 33 | t.equal(options.url, url) 34 | 35 | request.get(options, function(err, resp, body) { 36 | t.equal(err, null) 37 | t.equal(body, 'ok') 38 | t.equal(Object.keys(options).length, 1) 39 | t.equal(options.url, url) 40 | 41 | t.equal(methodsSeen.head, 1) 42 | t.equal(methodsSeen.get, 1) 43 | 44 | t.end() 45 | }) 46 | }) 47 | }) 48 | 49 | tape('cleanup', function(t) { 50 | s.close(function() { 51 | t.end() 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /tests/test-piped-redirect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var port1 = 6767 8 | , port2 = 6768 9 | 10 | var s1 = http.createServer(function(req, resp) { 11 | if (req.url === '/original') { 12 | resp.writeHeader(302, { 13 | 'location': '/redirected' 14 | }) 15 | resp.end() 16 | } else if (req.url === '/redirected') { 17 | resp.writeHeader(200, { 18 | 'content-type': 'text/plain' 19 | }) 20 | resp.write('OK') 21 | resp.end() 22 | } 23 | }) 24 | 25 | var s2 = http.createServer(function(req, resp) { 26 | var x = request('http://localhost:' + port1 + '/original') 27 | req.pipe(x) 28 | x.pipe(resp) 29 | }) 30 | 31 | tape('setup', function(t) { 32 | s1.listen(port1, function() { 33 | s2.listen(port2, function() { 34 | t.end() 35 | }) 36 | }) 37 | }) 38 | 39 | tape('piped redirect', function(t) { 40 | request('http://localhost:' + port2 + '/original', function(err, res, body) { 41 | t.equal(err, null) 42 | t.equal(body, 'OK') 43 | t.end() 44 | }) 45 | }) 46 | 47 | tape('cleanup', function(t) { 48 | s1.close(function() { 49 | s2.close(function() { 50 | t.end() 51 | }) 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /lib/defaults.js: -------------------------------------------------------------------------------- 1 | 2 | var extend = require('extend') 3 | var params = require('./params') 4 | 5 | 6 | function wrap (method, defaults, requester, verb) { 7 | 8 | return function (uri, options, callback) { 9 | var current = params.init(uri, options, callback) 10 | 11 | var all = {} 12 | extend(true, all, defaults, current) 13 | 14 | if (verb) { 15 | all.method = (verb === 'del' ? 'DELETE' : verb.toUpperCase()) 16 | } 17 | 18 | if (typeof requester === 'function') { 19 | method = requester 20 | } 21 | 22 | return method(all, all.callback) 23 | } 24 | } 25 | 26 | function defaults (options, requester) { 27 | var self = this 28 | options = options || {} 29 | 30 | if (typeof options === 'function') { 31 | requester = options 32 | options = {} 33 | } 34 | 35 | var defaults = wrap(self, options, requester) 36 | 37 | var verbs = ['get', 'head', 'post', 'put', 'patch', 'del'] 38 | verbs.forEach(function (verb) { 39 | defaults[verb] = wrap(self[verb], options, requester, verb) 40 | }) 41 | 42 | defaults.cookie = wrap(self.cookie, options, requester) 43 | defaults.jar = self.jar 44 | defaults.defaults = self.defaults 45 | 46 | return defaults 47 | } 48 | 49 | module.exports = defaults 50 | -------------------------------------------------------------------------------- /tests/test-follow-all.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var server = http.createServer(function (req, res) { 8 | // redirect everything 3 times, no matter what. 9 | var c = req.headers.cookie 10 | 11 | if (!c) { 12 | c = 0 13 | } else { 14 | c = +c.split('=')[1] || 0 15 | } 16 | 17 | if (c > 3) { 18 | res.end('ok') 19 | return 20 | } 21 | 22 | res.setHeader('set-cookie', 'c=' + (c + 1)) 23 | res.setHeader('location', req.url) 24 | res.statusCode = 302 25 | res.end('try again') 26 | }) 27 | 28 | tape('setup', function(t) { 29 | server.listen(6767, function() { 30 | t.end() 31 | }) 32 | }) 33 | 34 | tape('followAllRedirects', function(t) { 35 | var redirects = 0 36 | 37 | request.post({ 38 | url: 'http://localhost:6767/foo', 39 | followAllRedirects: true, 40 | jar: true, 41 | form: { foo: 'bar' } 42 | }, function (err, res, body) { 43 | t.equal(err, null) 44 | t.equal(body, 'ok') 45 | t.equal(redirects, 4) 46 | t.end() 47 | }).on('redirect', function() { 48 | redirects++ 49 | }) 50 | }) 51 | 52 | tape('cleanup', function(t) { 53 | server.close(function() { 54 | t.end() 55 | }) 56 | }) 57 | -------------------------------------------------------------------------------- /tests/test-emptyBody.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , http = require('http') 5 | , tape = require('tape') 6 | 7 | var s = http.createServer(function (req, resp) { 8 | resp.statusCode = 200 9 | resp.end('') 10 | }) 11 | 12 | tape('setup', function(t) { 13 | s.listen(6767, function() { 14 | t.end() 15 | }) 16 | }) 17 | 18 | tape('empty body with encoding', function(t) { 19 | request('http://localhost:6767', function(err, res, body) { 20 | t.equal(err, null) 21 | t.equal(res.statusCode, 200) 22 | t.equal(body, '') 23 | t.end() 24 | }) 25 | }) 26 | 27 | tape('empty body without encoding', function(t) { 28 | request({ 29 | url: 'http://localhost:6767', 30 | encoding: null 31 | }, function(err, res, body) { 32 | t.equal(err, null) 33 | t.equal(res.statusCode, 200) 34 | t.same(body, new Buffer(0)) 35 | t.end() 36 | }) 37 | }) 38 | 39 | tape('empty JSON body', function(t) { 40 | request({ 41 | url: 'http://localhost:6767', 42 | json: {} 43 | }, function(err, res, body) { 44 | t.equal(err, null) 45 | t.equal(res.statusCode, 200) 46 | t.equal(body, undefined) 47 | t.end() 48 | }) 49 | }) 50 | 51 | tape('cleanup', function(t) { 52 | s.close(function() { 53 | t.end() 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /tests/browser/start.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var spawn = require('child_process').spawn 3 | var https = require('https') 4 | var fs = require('fs') 5 | var path = require('path') 6 | 7 | var port = 6767 8 | 9 | var server = https.createServer({ 10 | key: fs.readFileSync(path.join(__dirname, '/ssl/server.key')), 11 | cert: fs.readFileSync(path.join(__dirname, '/ssl/server.crt')), 12 | ca: fs.readFileSync(path.join(__dirname, '/ssl/ca.crt')), 13 | requestCert: true, 14 | rejectUnauthorized: false 15 | }, function (req, res) { 16 | // Set CORS header, since that is something we are testing. 17 | res.setHeader('Access-Control-Allow-Origin', '*') 18 | res.writeHead(200) 19 | res.end('Can you hear the sound of an enormous door slamming in the depths of hell?\n') 20 | }) 21 | server.listen(port, function() { 22 | console.log('Started https server for karma tests on port ' + port) 23 | // Spawn process for karma. 24 | var c = spawn('karma', [ 25 | 'start', 26 | path.join(__dirname, '/karma.conf.js') 27 | ]) 28 | c.stdout.pipe(process.stdout) 29 | c.stderr.pipe(process.stderr) 30 | c.on('exit', function(c) { 31 | // Exit process with karma exit code. 32 | if (c !== 0) { 33 | throw new Error('Karma exited with status code ' + c) 34 | } 35 | server.close() 36 | }) 37 | }) 38 | -------------------------------------------------------------------------------- /lib/base-url.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = function baseURL (req, options) { 3 | options.url = options.url || options.uri 4 | 5 | if (typeof options.baseUrl !== 'string') { 6 | return req.emit('error', new Error('options.baseUrl must be a string')) 7 | } 8 | 9 | if (typeof options.url !== 'string') { 10 | return req.emit('error', new Error('options.uri must be a string when using options.baseUrl')) 11 | } 12 | 13 | if (options.url.indexOf('//') === 0 || options.url.indexOf('://') !== -1) { 14 | return req.emit('error', new Error('options.uri must be a path when using options.baseUrl')) 15 | } 16 | 17 | // Handle all cases to make sure that there's only one slash between 18 | // baseUrl and uri. 19 | var baseUrlEndsWithSlash = options.baseUrl.lastIndexOf('/') === options.baseUrl.length - 1 20 | var uriStartsWithSlash = options.url.indexOf('/') === 0 21 | 22 | if (baseUrlEndsWithSlash && uriStartsWithSlash) { 23 | options.url = options.baseUrl + options.url.slice(1) 24 | } 25 | else if (baseUrlEndsWithSlash || uriStartsWithSlash) { 26 | options.url = options.baseUrl + options.url 27 | } 28 | else if (options.url === '') { 29 | options.url = options.baseUrl 30 | } 31 | else { 32 | options.url = options.baseUrl + '/' + options.url 33 | } 34 | delete options.baseUrl 35 | } 36 | -------------------------------------------------------------------------------- /tests/test-promise.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | , Promise = require('bluebird') 7 | 8 | var s = http.createServer(function(req, res) { 9 | res.writeHead(200, {}) 10 | res.end('ok') 11 | }) 12 | 13 | tape('setup', function(t) { 14 | s.listen(6767, function() { 15 | t.end() 16 | }) 17 | }) 18 | 19 | tape('promisify convenience method', function(t) { 20 | var get = request.get 21 | var p = Promise.promisify(get, {multiArgs: true}) 22 | p('http://localhost:6767') 23 | .then(function (results) { 24 | var res = results[0] 25 | t.equal(res.statusCode, 200) 26 | t.end() 27 | }) 28 | }) 29 | 30 | tape('promisify request function', function(t) { 31 | var p = Promise.promisify(request, {multiArgs: true}) 32 | p('http://localhost:6767') 33 | .spread(function (res, body) { 34 | t.equal(res.statusCode, 200) 35 | t.end() 36 | }) 37 | }) 38 | 39 | tape('promisify all methods', function(t) { 40 | Promise.promisifyAll(request, {multiArgs: true}) 41 | request.getAsync('http://localhost:6767') 42 | .spread(function (res, body) { 43 | t.equal(res.statusCode, 200) 44 | t.end() 45 | }) 46 | }) 47 | 48 | tape('cleanup', function(t) { 49 | s.close(function() { 50 | t.end() 51 | }) 52 | }) 53 | -------------------------------------------------------------------------------- /tests/browser/karma.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var istanbul = require('browserify-istanbul') 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | basePath: '../..', 7 | frameworks: ['tap', 'browserify'], 8 | preprocessors: { 9 | 'tests/browser/test.js': ['browserify'], 10 | '*.js,!(tests)/**/*.js': ['coverage'] 11 | }, 12 | files: [ 13 | 'tests/browser/test.js' 14 | ], 15 | port: 9876, 16 | 17 | reporters: ['dots', 'coverage'], 18 | 19 | colors: true, 20 | 21 | logLevel: config.LOG_ERROR, 22 | 23 | autoWatch: false, 24 | 25 | browsers: ['PhantomJS_without_security'], 26 | 27 | singleRun: true, 28 | 29 | plugins: [ 30 | 'karma-phantomjs-launcher', 31 | 'karma-coverage', 32 | 'karma-browserify', 33 | 'karma-tap' 34 | ], 35 | browserify: { 36 | debug: true, 37 | transform: [istanbul({ 38 | ignore: ['**/node_modules/**', '**/tests/**'] 39 | })] 40 | }, 41 | coverageReporter: { 42 | type: 'lcov', 43 | dir: 'coverage/' 44 | }, 45 | 46 | // Custom launcher to allowe self signed certs. 47 | customLaunchers: { 48 | PhantomJS_without_security: { 49 | base: 'PhantomJS', 50 | flags: [ 51 | '--ignore-ssl-errors=true' 52 | ] 53 | } 54 | } 55 | }) 56 | } 57 | -------------------------------------------------------------------------------- /lib/cookie.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var tough = require('tough-cookie') 4 | 5 | 6 | function RequestJar (store) { 7 | this._jar = new tough.CookieJar(store, {looseMode: true}) 8 | } 9 | 10 | RequestJar.prototype.setCookie = function (cookieOrStr, uri, options) { 11 | return this._jar.setCookieSync(cookieOrStr, uri, options || {}) 12 | } 13 | 14 | RequestJar.prototype.getCookieString = function (uri) { 15 | return this._jar.getCookieStringSync(uri) 16 | } 17 | 18 | RequestJar.prototype.getCookies = function (uri) { 19 | return this._jar.getCookiesSync(uri) 20 | } 21 | 22 | RequestJar.prototype.setCookieSync = function (cookieOrStr, uri, options) { 23 | return tough.CookieJar.prototype.setCookieSync.call(this._jar, cookieOrStr, uri, options || {}) 24 | } 25 | RequestJar.prototype.getCookieStringSync = function (uri) { 26 | return tough.CookieJar.prototype.getCookieStringSync.call(this._jar, uri) 27 | } 28 | RequestJar.prototype.getCookiesSync = function (uri) { 29 | return tough.CookieJar.prototype.getCookiesSync.call(this._jar, uri) 30 | } 31 | 32 | 33 | exports.parse = function (str) { 34 | if (str && str.uri) { 35 | str = str.uri 36 | } 37 | if (typeof str !== 'string') { 38 | throw new Error('The cookie function only accepts STRING as param') 39 | } 40 | return tough.Cookie.parse(str, {loose: true}) 41 | } 42 | 43 | exports.jar = function (store) { 44 | return new RequestJar(store) 45 | } 46 | -------------------------------------------------------------------------------- /tests/test-hawk.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , hawk = require('hawk') 6 | , tape = require('tape') 7 | , assert = require('assert') 8 | 9 | var server = http.createServer(function(req, res) { 10 | var getCred = function(id, callback) { 11 | assert.equal(id, 'dh37fgj492je') 12 | var credentials = { 13 | key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', 14 | algorithm: 'sha256', 15 | user: 'Steve' 16 | } 17 | return callback(null, credentials) 18 | } 19 | 20 | hawk.server.authenticate(req, getCred, {}, function(err, credentials, attributes) { 21 | res.writeHead(err ? 401 : 200, { 22 | 'Content-Type': 'text/plain' 23 | }) 24 | res.end(err ? 'Shoosh!' : 'Hello ' + credentials.user) 25 | }) 26 | }) 27 | 28 | tape('setup', function(t) { 29 | server.listen(6767, function() { 30 | t.end() 31 | }) 32 | }) 33 | 34 | tape('hawk', function(t) { 35 | var creds = { 36 | key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn', 37 | algorithm: 'sha256', 38 | id: 'dh37fgj492je' 39 | } 40 | request('http://localhost:6767', { 41 | hawk: { credentials: creds } 42 | }, function(err, res, body) { 43 | t.equal(err, null) 44 | t.equal(res.statusCode, 200) 45 | t.equal(body, 'Hello Steve') 46 | t.end() 47 | }) 48 | }) 49 | 50 | tape('cleanup', function(t) { 51 | server.close(function() { 52 | t.end() 53 | }) 54 | }) 55 | -------------------------------------------------------------------------------- /tests/test-localAddress.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | var request = require('../index') 3 | , tape = require('tape') 4 | 5 | tape('bind to invalid address', function (t) { 6 | request.get({ 7 | uri: 'http://www.google.com', 8 | localAddress: '1.2.3.4' 9 | }, function (err, res) { 10 | t.notEqual(err, null) 11 | t.equal(true, /bind EADDRNOTAVAIL/.test(err.message)) 12 | t.equal(res, undefined) 13 | t.end() 14 | }) 15 | }) 16 | 17 | tape('bind to local address', function (t) { 18 | request.get({ 19 | uri: 'http://www.google.com', 20 | localAddress: '127.0.0.1' 21 | }, function (err, res) { 22 | t.notEqual(err, null) 23 | t.equal(res, undefined) 24 | t.end() 25 | }) 26 | }) 27 | 28 | tape('bind to local address on redirect', function (t) { 29 | var os = require('os') 30 | var localInterfaces = os.networkInterfaces() 31 | var localIPS = [] 32 | Object.keys(localInterfaces).forEach(function (ifname) { 33 | localInterfaces[ifname].forEach(function (iface) { 34 | if (iface.family !== 'IPv4' || iface.internal !== false) { 35 | // skip over internal (i.e. 127.0.0.1) and non-ipv4 addresses 36 | return 37 | } 38 | localIPS.push(iface.address) 39 | }) 40 | }) 41 | request.get({ 42 | uri: 'http://google.com', //redirects to 'http://google.com' 43 | localAddress: localIPS[0] 44 | }, function (err, res) { 45 | t.equal(err, null) 46 | t.equal(res.request.localAddress, localIPS[0]) 47 | t.end() 48 | }) 49 | }) 50 | -------------------------------------------------------------------------------- /tests/test-agentOptions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.running_under_istanbul) { 4 | // test-agent.js modifies the process state 5 | // causing these tests to fail when running under single process via tape 6 | return 7 | } 8 | 9 | var request = require('../index') 10 | , http = require('http') 11 | , server = require('./server') 12 | , tape = require('tape') 13 | 14 | var s = server.createServer(function (req, resp) { 15 | resp.statusCode = 200 16 | resp.end('') 17 | }) 18 | 19 | tape('setup', function(t) { 20 | s.listen(s.port, function() { 21 | t.end() 22 | }) 23 | }) 24 | 25 | tape('without agentOptions should use global agent', function(t) { 26 | var r = request(s.url, function(/*err, res, body*/) { 27 | // TODO: figure out why err.code === 'ECONNREFUSED' on Travis? 28 | //if (err) console.log(err) 29 | //t.equal(err, null) 30 | t.deepEqual(r.agent, http.globalAgent) 31 | t.equal(Object.keys(r.pool).length, 0) 32 | t.end() 33 | }) 34 | }) 35 | 36 | tape('with agentOptions should apply to new agent in pool', function(t) { 37 | var r = request(s.url, { 38 | agentOptions: { foo: 'bar' } 39 | }, function(/*err, res, body*/) { 40 | // TODO: figure out why err.code === 'ECONNREFUSED' on Travis? 41 | //if (err) console.log(err) 42 | //t.equal(err, null) 43 | t.equal(r.agent.options.foo, 'bar') 44 | t.equal(Object.keys(r.pool).length, 1) 45 | t.end() 46 | }) 47 | }) 48 | 49 | tape('cleanup', function(t) { 50 | s.close(function() { 51 | t.end() 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /tests/test-onelineproxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , assert = require('assert') 5 | , request = require('../index') 6 | , tape = require('tape') 7 | 8 | var server = http.createServer(function(req, resp) { 9 | resp.statusCode = 200 10 | if (req.url === '/get') { 11 | assert.equal(req.method, 'GET') 12 | resp.write('content') 13 | resp.end() 14 | return 15 | } 16 | if (req.url === '/put') { 17 | var x = '' 18 | assert.equal(req.method, 'PUT') 19 | req.on('data', function(chunk) { 20 | x += chunk 21 | }) 22 | req.on('end', function() { 23 | assert.equal(x, 'content') 24 | resp.write('success') 25 | resp.end() 26 | }) 27 | return 28 | } 29 | if (req.url === '/proxy') { 30 | assert.equal(req.method, 'PUT') 31 | req.pipe(request('http://localhost:6767/put')).pipe(resp) 32 | return 33 | } 34 | if (req.url === '/test') { 35 | request('http://localhost:6767/get').pipe(request.put('http://localhost:6767/proxy')).pipe(resp) 36 | return 37 | } 38 | throw new Error('Unknown url', req.url) 39 | }) 40 | 41 | tape('setup', function(t) { 42 | server.listen(6767, function() { 43 | t.end() 44 | }) 45 | }) 46 | 47 | tape('chained one-line proxying', function(t) { 48 | request('http://localhost:6767/test', function(err, res, body) { 49 | t.equal(err, null) 50 | t.equal(res.statusCode, 200) 51 | t.equal(body, 'success') 52 | t.end() 53 | }) 54 | }) 55 | 56 | tape('cleanup', function(t) { 57 | server.close(function() { 58 | t.end() 59 | }) 60 | }) 61 | -------------------------------------------------------------------------------- /tests/test-timing.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var server = require('./server') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var plain_server = server.createServer() 8 | , redirect_mock_time = 10 9 | 10 | tape('setup', function(t) { 11 | plain_server.listen(plain_server.port, function() { 12 | plain_server.on('/', function (req, res) { 13 | res.writeHead(200) 14 | res.end('plain') 15 | }) 16 | plain_server.on('/redir', function (req, res) { 17 | // fake redirect delay to ensure strong signal for rollup check 18 | setTimeout(function() { 19 | res.writeHead(301, { 'location': 'http://localhost:' + plain_server.port + '/' }) 20 | res.end() 21 | }, redirect_mock_time) 22 | }) 23 | 24 | t.end() 25 | }) 26 | }) 27 | 28 | tape('non-redirected request is timed', function(t) { 29 | var options = {time: true} 30 | request('http://localhost:' + plain_server.port + '/', options, function(err, res, body) { 31 | t.equal(err, null) 32 | t.equal(typeof res.elapsedTime, 'number') 33 | t.equal((res.elapsedTime > 0), true) 34 | t.end() 35 | }) 36 | }) 37 | 38 | tape('redirected request is timed with rollup', function(t) { 39 | var options = {time: true} 40 | request('http://localhost:' + plain_server.port + '/redir', options, function(err, res, body) { 41 | t.equal(err, null) 42 | t.equal(typeof res.elapsedTime, 'number') 43 | t.equal((res.elapsedTime > 0), true) 44 | t.equal((res.elapsedTime > redirect_mock_time), true) 45 | t.end() 46 | }) 47 | }) 48 | 49 | tape('cleanup', function(t) { 50 | plain_server.close(function() { 51 | t.end() 52 | }) 53 | }) 54 | -------------------------------------------------------------------------------- /tests/ssl/ca/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEpAIBAAKCAQEA2EpK+JbLMaJeMtEP9Zo/UtRQGe6mcfaZiunKgSpoXVC6fUbo 3 | nGFFBttR2wD+hAGrc40d9ygICJQdY6QH0xLfkaskawDnXY2IwVp1VY4w8krwAcAQ 4 | AnsAJ4aQCqrhGbqSgEoQGbbvBt1tlu107WTJMlv/+hC7tbGaIwaGfTNUx+xDbhu7 5 | 4y2uw1R3eqqrWclRQ/BYwDf8nq3yZue7fSvYeh5mX8L9SE9zwrcrSsBMrWsTEwwS 6 | bl3p+vC6JrM5RF5dipx50jaBEaaDdDcUCI/uLMg3+ucUx5CeRL9mXBljqjGTSmzv 7 | psSWF+a+LpPBnLoLQ+SN6yFV/LGTSq0Bc6RZwQIDAQABAoIBAGEj7Mv9HcFrBReZ 8 | oatS3YHb7SXYc1TXxloHanXckAbpDPja8fnaDeBofDj6F1U+UryQ8pZgmksQCqsH 9 | rqPz5AlObgrI2yC/Ql5kvDHyrLUFRwniMs6KY6Vc4DCKUpL1onqPyO9jo7LXnDKe 10 | 71b3Xw2JGEw9W7Dc1TdJ5PkyJq+q7wlvrGuXvr6gjDZGNFjc4qD2p3UkGzV/AVa/ 11 | DFY2EJcP0H3SSYPpjN3GAPDelBG/5a/kGLp2U+9wxK682/ZKORuS0d+/AZY3XX3l 12 | WTy4a0Lmmeunyy/fkMuI5MkNTiTaU90FnivMrLq/9j2HWJCu8QKwwMHvE4Bv0QJM 13 | UVSFaOkCgYEA/vrs01oXaIpf2bv3uAHmKauIQW7D7+fcvZudq5rvKqnx6eSyI3E1 14 | CLzLi4EkVTthUojRAPLnEP93EjI32rZr2lr71PZTY2MAEi/NoPTCjj1fjJvPcumS 15 | xfVeJs5RINCk09Cb21FjlSddk7uuGJgVtTrZpX+6qh7LNbjW4wCyuI8CgYEA2SfA 16 | w/Fv8Rsy+Cxwg6RGWDKnKUEJT9Ow2DQXBCGaXFNuidNj3Wv+bEgMTYl++oWA0yML 17 | 3uSou4jsjEi6qcKDT/o1ZGOB1RU4JO17h8Jc0BXwjQPkwy5iT9INfUD7tGbp5CHo 18 | XFpu95YPJlSmrDN9lUBcO83xv4KDZMUoNV480K8CgYEAqONplECbOqpU/LJtTVss 19 | qbMtaDHG5JQOeSSnFfBktDymuMa7W5BzkVsD815Rw4a2WuW2kktR08dyhgHvTxX/ 20 | cD1NiuyxpSYA+Qrix9b3OyHZtRfLG5Esn6R7fXaw8+xfENGfOnC5ZiUR7XWlxjKO 21 | RmE5ok5tRJtq/CV3aBqhRm8CgYEA1/ZiDjyaIIX1Tb0cdL82Ola9yhhlA1+7m3lK 22 | fpBQrItI/ocd5UKWt+d7XM1mXA3TjadoEdcEO+Wzotxdz6Cj6TEkUl9n6pt8x7Tq 23 | ypwwo71+CzAZHUeO/GUhhzTOXp6O85QJO3ewrkgtbuh3DgDzXzCvycZKKzTIKbqt 24 | /01mW/8CgYABbHvNMZiaARow1yeKifz0dSEKWuym59VFdqzXKsCvx0iSe2QawKOV 25 | LgFubIgmDZZJ0GwjBEuLv/NMFwHPfUfvNtUm053HAfJSVtk92VyrmUCODIygoOm9 26 | O2jxpRnIM/KfwszTzge1eWEJGA8xlTmL+Hud/3ofBqXbx/RWrM/hAA== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /tests/ssl/ca/localhost.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEArl3598SpzKRa/87o4u+x2/fLaH+MrZGUThJKCqEKhLYnhfTw 3 | HosYc6z4hww60ZXb10vL/Ccwp2pVz8KoZ3QoHrORURFa3QeZyhPcBN4hyzyTI76L 4 | 74tAH7lg1xJuZdkTiF786gxYsnAsXNsLNIcJkZ262cA7ymV1XUBYwpidykNCPFU4 5 | vYmLayAkVrwl+XZzpvOlgnEetb5t1aGrtCMiozhO1xGSZbg0YyZ/aw2dMLmm8jr9 6 | sW9BJKBhDlmNHrK28XF7KjRzDJblrNN2CRHNcdqfabYOHhuRwu71F5FW4no/p8BO 7 | tRAP3WYzzkFvxDOf4ves4pK4ByaDHzCtMfKN5QIDAQABAoIBAHTuJoxGQQwwB6pW 8 | agyNazu0755TMtbOsqKsVyTLnA8lTFnjyQbihnJEQ6HkzKjyEyxM8y1UZqdOgt9B 9 | jcdauPDlwISZ29Ivn61JJhnJkOYG6DFnPdZVDpp3qX5xKMF6EkQ4VujpgK2g1c8r 10 | QVdnWz5ghQYziKUQ5uSzGxLcX6xb1Pc/YOr1Vy8agBjNy5Fnre4Dgv2w0ki7JL2d 11 | x9LA0Qe+JDbGb/rDy3Xl1msNboglWNANtQIq+kWiujvqpBijdP2Os3KvyaKGy8V3 12 | gB750rV6mWtflDA2KEt0hTnDqRtBj3Y/i1RqYux5bs1KaIMxdNhRwyS/6BQt7+rg 13 | F535GSECgYEA1DMv3tKtnFrGA78Htsy6TjtCC0P4jwZw2Xj+pLEu2KInw1JMMSzQ 14 | rpkkFhBOlkg8NhFmEbE9BovR7cS24PDIq+Ya2M3l5VQWYnXEOQZH7MOCv+No4bEA 15 | XGPnKDX3VN0UJblkQJwxfGCcETvJaQra89kybEQZu2JAkypOzRs5r00CgYEA0lur 16 | 6vrG85xNwoMns6PNDFWgV9yl6hKoDLZTfECwYh26Znp2gXrGJ/UAY3FcBeQprDzh 17 | T6eKcegqU+kp4v2Hc9WyyluupUlcGWLbCaTTsE2eUgr/0K3LxVqrLg6VF6fG0HZa 18 | sconJx8HhbzHqniVvwjaOyt0A23/g5ivarpFPPkCgYBCuqoGFxhTP9RfHzaMzIqV 19 | yVq2cjR6vZrFOKBTKAjERRHeAUZGfIJPNYc8jPo5lhOhKQ2A6Mx4/4UPkTm1OOLR 20 | 87VjkjQGTtAPPFttV0VM9hpqv1efCWtEooHxii7x9+e7CTa2fqetJjBN1xA6QRij 21 | cBzEIRI6c+Y8oSRQqYwVTQKBgQCs/ka7z9CdtwUb2dBko2iVpDVhDExF22HoUmkF 22 | 3g0wI1KPxFaA1P7xDUNshGUxUxoSU17XqujoFA37Q9z2l5k1YaDPWeaed14OYoXP 23 | wIV2j96LihAnBUZ23sG39rYV5hxSg4LCg4T/Xz1Idp+dSd2cZSNTVcDqsSNYjdB0 24 | 7QrTwQKBgGTRximBEZj6anNQXxlGUJFd6GZy0UWg4yZf8GH9pCD7PxvDSJNQnVQ1 25 | nNvdpAFmAcUg6oFP4UTgvf+yGj5L1j5qRicaPiOTT+kDIJ+mRH1lSuMnoTn0Kd0v 26 | /qaX8EqP15UjLfAbUBuz0oQLksGqLidYQOjbGI8xW82/i4mj7V+A 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@request/legacy", 3 | "version": "0.1.2", 4 | "description": "HTTP Client Library", 5 | "keywords": [ 6 | "request", 7 | "http", 8 | "https", 9 | "client" 10 | ], 11 | "license": "Apache-2.0", 12 | "homepage": "https://github.com/request/legacy", 13 | "author": "Simeon Velichkov (http://simov.github.io)", 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/request/legacy.git" 17 | }, 18 | "dependencies": { 19 | "@request/core": "~0.1.0", 20 | "@request/digest": "~0.1.0", 21 | "@request/headers": "~0.1.0", 22 | "@request/multipart": "~0.1.0", 23 | "@request/oauth": "~0.1.0", 24 | "http-signature": "~1.0.2", 25 | "hawk": "~3.1.0", 26 | "aws-sign2": "~0.6.0", 27 | "iconv-lite": "~0.4.13", 28 | "tough-cookie": "~2.2.0", 29 | "tunnel-agent": "~0.4.1", 30 | "extend": "~3.0.0", 31 | "har-validator": "~2.0.2" 32 | }, 33 | "devDependencies": { 34 | "bluebird": "^3.0.2", 35 | "browserify": "^12.0.1", 36 | "browserify-istanbul": "^0.1.5", 37 | "buffer-equal": "^0.0.1", 38 | "eslint": "1.9.0", 39 | "function-bind": "^1.0.2", 40 | "istanbul": "^0.4.0", 41 | "karma": "^0.13.10", 42 | "karma-browserify": "^4.4.0", 43 | "karma-cli": "^0.1.1", 44 | "karma-coverage": "^0.5.3", 45 | "karma-phantomjs-launcher": "^0.1.4", 46 | "karma-tap": "^1.0.3", 47 | "minimist": "^1.2.0", 48 | "rimraf": "^2.2.8", 49 | "server-destroy": "^1.0.1", 50 | "tape": "^4.2.0", 51 | "taper": "^0.4.0" 52 | }, 53 | "main": "index.js", 54 | "files": [ 55 | "lib/", 56 | "LICENSE", 57 | "README.md", 58 | "index.js" 59 | ], 60 | "scripts": { 61 | "test": "npm run test-ci", 62 | "test-ci": "node tests/core.js -m tape" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/ssl/ca/client-enc.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: AES-128-CBC,DFE9B15161553AFAA662AC7EACFB6D35 4 | 5 | zGaKZze08HgD7HDV+s9etBsPelQ9o8iMslZNi5NKtyyG54ivITgZpVmPVr164+J7 6 | xJPDbHPLvW+a5K8gyNrZKrRuZHBWcggN3IPzTP1Q02nIb4uhgJUHFSOOspYKWZwD 7 | KnBOUKO52y7FFYF1ZnLdJBjN1+ImjR5H/3EI51YirNis+9fKtYHCRGRC9BpA3Mub 8 | ccxETSAc22ZP7yXyY+JRXx4AikTPiOX5b574MLj1o4BH+Snb8T4EnvNoDcg7rwl0 9 | 01UGdOLFy+ZecLOXAtn3Ta4n+G5SkHX/09Z57RbtNGwRXyynYCYAy6+Sx+sn5+QL 10 | 6L1wzk766iOq1a84jqz+SWEVA/HMHsNivtx0vom1GfqQwLLjaSW5T+dAD1ZEClqs 11 | IFAj41wNdOwKxvHTTUeNIud0XWSYlmdbF1VUOdtbOzeCtz99pEpC6HeghtgZlNuD 12 | IzdlrLU8jrjDMVNrBF7fYQ4Lje1j5G83vZWMQF2/MjIExOcbAV7SkFIcVuBdnSZG 13 | zYKAqR++NvwQWxSEHoBbkl+KRibojdfpzPFdm9HThUxILeWr7RjQ5CVohIC+ZBiv 14 | AsJx1K0IHxAcbteniZGTK2OkhDCWBcGd0mAgi6ma+nX1pgYvKwqTWshSOD8dTdzi 15 | p7aonn52I6hPv0RKRnL4NJYeN/GUgcDAMLUv2fpMudo1W0uCp13zKKDnOkTchz+m 16 | evVqgQB5Dgu+bktbxjLAxYo+/3aTjWWtxjVLx7le2HpDAbd8BJ8+T10zK8aL2FZX 17 | lCSnb4ei27ohBAZpQ/oONSp/8V3Cv4+TyDILnmGPkfd0swE3YV5plxlsvkVAx3qQ 18 | 37VbJ8Ya54zfTcyOqLj6Ave9wWaL+so4Hw7pobEDmqgeW1RY62yhQ0Wlhc6iWFrB 19 | tjixs/OM1eAsfW7QPv2SfNdNrakJCd9hqU2SMCw9RPOoVXU7DmSZMYl2Gn6XjwYn 20 | Gn/VTKwyx/+JUTDnDbSgJNbXIBcNJGXFfXa0Pso2XBlX4uP638MQ5Ofdtez6+aPX 21 | fKquJLB2qPfVXyB7yZOKZLA0im3ckp2xS5nKTT7EqKLv7ZZss7tJSWfFAITAhxsk 22 | AeDrcwsEzqi5bdNaoAo+5GWXBCoLB0vvUkXFRQpfctAd+3zVs/Afr3s4j7HOLMZZ 23 | MAQl/ITjSjwNUbsbv/xpoozY75fEfQ5zKR/Buj1yfwWYYTA4/5BswJs9V/Lex/mG 24 | O7IDlzRLQXYOdKI6zT6Zsxfy+oHuLWOt29/N/xoJPRythy8gskjp3R+rDN02NBi8 25 | x/00ev3P2BQ7/Giiz2lKklOBo8qmyPE+VvW4fmTaAYpaHor6+gxnvtM9FDE5FEGV 26 | PIXuRNPftR9A8N4bUO14bqiIcCwzSb5ncxqVQOiNdC+JhgmF3kcYt2bAhaYN9dQB 27 | F2cloSoQN5oSKFiyRf2tQDB5/VXOf59/UvIVe7Nmsey2JTDCCwTc+S9mjixw0yn5 28 | BEb2pjWa2C5Bby6LZFu44hpU0cogbYW+dZtqJuDUVsXtfPGIP8R4xnSRIyYrwoQc 29 | tqoxSAvmVC0zEJEmFhLPl+gwYUy5grUZnzR7GSMwC0Sn9i1989jC4XCPrEDS/Y6m 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /tests/test-form-urlencoded.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | 8 | function runTest (t, options, index) { 9 | 10 | var server = http.createServer(function(req, res) { 11 | 12 | if (index === 0 || index === 3) { 13 | t.equal(req.headers['content-type'], 'application/x-www-form-urlencoded') 14 | } else { 15 | t.equal(req.headers['content-type'], 'application/x-www-form-urlencoded; charset=UTF-8') 16 | } 17 | t.equal(req.headers['content-length'], '21') 18 | t.equal(req.headers.accept, 'application/json') 19 | 20 | var data = '' 21 | req.setEncoding('utf8') 22 | 23 | req.on('data', function(d) { 24 | data += d 25 | }) 26 | 27 | req.on('end', function() { 28 | t.equal(data, 'some=url&encoded=data') 29 | 30 | res.writeHead(200) 31 | res.end('done') 32 | }) 33 | }) 34 | 35 | server.listen(6767, function() { 36 | 37 | var r = request.post('http://localhost:6767', options, function(err, res, body) { 38 | t.equal(err, null) 39 | t.equal(res.statusCode, 200) 40 | t.equal(body, 'done') 41 | server.close(function() { 42 | t.end() 43 | }) 44 | }) 45 | if (!options.form && !options.body) { 46 | r.form({some: 'url', encoded: 'data'}) 47 | } 48 | }) 49 | } 50 | 51 | var cases = [ 52 | { 53 | form: {some: 'url', encoded: 'data'}, 54 | json: true 55 | }, 56 | { 57 | headers: {'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'}, 58 | form: {some: 'url', encoded: 'data'}, 59 | json: true 60 | }, 61 | { 62 | headers: {'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'}, 63 | body: 'some=url&encoded=data', 64 | json: true 65 | }, 66 | { 67 | // body set via .form() method 68 | json: true 69 | } 70 | ] 71 | 72 | cases.forEach(function (options, index) { 73 | tape('application/x-www-form-urlencoded ' + index, function(t) { 74 | runTest(t, options, index) 75 | }) 76 | }) 77 | -------------------------------------------------------------------------------- /tests/test-redirect-complex.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var server = require('./server') 4 | , request = require('../index') 5 | , events = require('events') 6 | , tape = require('tape') 7 | 8 | var s = server.createServer() 9 | , ss = server.createSSLServer() 10 | , e = new events.EventEmitter() 11 | 12 | function bouncy(s, serverUrl) { 13 | var redirs = { a: 'b' 14 | , b: 'c' 15 | , c: 'd' 16 | , d: 'e' 17 | , e: 'f' 18 | , f: 'g' 19 | , g: 'h' 20 | , h: 'end' } 21 | 22 | var perm = true 23 | Object.keys(redirs).forEach(function (p) { 24 | var t = redirs[p] 25 | 26 | // switch type each time 27 | var type = perm ? 301 : 302 28 | perm = !perm 29 | s.on('/' + p, function (req, res) { 30 | setTimeout(function() { 31 | res.writeHead(type, { location: serverUrl + '/' + t }) 32 | res.end() 33 | }, Math.round(Math.random() * 25)) 34 | }) 35 | }) 36 | 37 | s.on('/end', function (req, res) { 38 | var key = req.headers['x-test-key'] 39 | e.emit('hit-' + key, key) 40 | res.writeHead(200) 41 | res.end(key) 42 | }) 43 | } 44 | 45 | tape('setup', function(t) { 46 | s.listen(s.port, function() { 47 | bouncy(s, ss.url) 48 | ss.listen(ss.port, function() { 49 | bouncy(ss, s.url) 50 | t.end() 51 | }) 52 | }) 53 | }) 54 | 55 | tape('lots of redirects', function(t) { 56 | var n = 10 57 | t.plan(n * 4) 58 | 59 | function doRedirect(i) { 60 | var key = 'test_' + i 61 | request({ 62 | url: (i % 2 ? s.url : ss.url) + '/a', 63 | headers: { 'x-test-key': key }, 64 | rejectUnauthorized: false 65 | }, function(err, res, body) { 66 | t.equal(err, null) 67 | t.equal(res.statusCode, 200) 68 | t.equal(body, key) 69 | }) 70 | 71 | e.once('hit-' + key, function(v) { 72 | t.equal(v, key) 73 | }) 74 | } 75 | 76 | for (var i = 0; i < n; i++) { 77 | doRedirect(i) 78 | } 79 | }) 80 | 81 | tape('cleanup', function(t) { 82 | s.close(function() { 83 | ss.close(function() { 84 | t.end() 85 | }) 86 | }) 87 | }) 88 | -------------------------------------------------------------------------------- /tests/test-proxy-connect.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , tape = require('tape') 5 | 6 | var port = 6768 7 | , called = false 8 | , proxiedHost = 'google.com' 9 | , data = '' 10 | 11 | var s = require('net').createServer(function(sock) { 12 | called = true 13 | sock.once('data', function (c) { 14 | data += c 15 | 16 | sock.write('HTTP/1.1 200 OK\r\n\r\n') 17 | 18 | sock.once('data', function (c) { 19 | data += c 20 | 21 | sock.write('HTTP/1.1 200 OK\r\n') 22 | sock.write('content-type: text/plain\r\n') 23 | sock.write('content-length: 5\r\n') 24 | sock.write('\r\n') 25 | sock.end('derp\n') 26 | }) 27 | }) 28 | }) 29 | 30 | tape('setup', function(t) { 31 | s.listen(port, function() { 32 | t.end() 33 | }) 34 | }) 35 | 36 | tape('proxy', function(t) { 37 | request({ 38 | tunnel: true, 39 | url: 'http://' + proxiedHost, 40 | proxy: 'http://localhost:' + port, 41 | headers: { 42 | 'Proxy-Authorization' : 'Basic dXNlcjpwYXNz', 43 | 'authorization' : 'Token deadbeef', 44 | 'dont-send-to-proxy' : 'ok', 45 | 'dont-send-to-dest' : 'ok', 46 | 'accept' : 'yo', 47 | 'user-agent' : 'just another foobar' 48 | }, 49 | proxyHeaderExclusiveList: ['Dont-send-to-dest'] 50 | }, function(err, res, body) { 51 | t.equal(err, null) 52 | t.equal(res.statusCode, 200) 53 | t.equal(body, 'derp\n') 54 | var re = new RegExp([ 55 | 'CONNECT google.com:80 HTTP/1.1', 56 | 'Proxy-Authorization: Basic dXNlcjpwYXNz', 57 | 'dont-send-to-dest: ok', 58 | 'accept: yo', 59 | 'user-agent: just another foobar', 60 | 'host: google.com:80', 61 | 'Connection: close', 62 | '', 63 | 'GET / HTTP/1.1', 64 | 'authorization: Token deadbeef', 65 | 'dont-send-to-proxy: ok', 66 | 'accept: yo', 67 | 'user-agent: just another foobar', 68 | 'host: google.com' 69 | ].join('\r\n')) 70 | t.equal(true, re.test(data)) 71 | t.equal(called, true, 'the request must be made to the proxy server') 72 | t.end() 73 | }) 74 | }) 75 | 76 | tape('cleanup', function(t) { 77 | s.close(function() { 78 | t.end() 79 | }) 80 | }) 81 | -------------------------------------------------------------------------------- /tests/test-unix.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , http = require('http') 5 | , fs = require('fs') 6 | , rimraf = require('rimraf') 7 | , assert = require('assert') 8 | , tape = require('tape') 9 | , url = require('url') 10 | 11 | var rawPath = [null, 'raw', 'path'].join('/') 12 | , queryPath = [null, 'query', 'path'].join('/') 13 | , searchString = '?foo=bar' 14 | , socket = [__dirname, 'tmp-socket'].join('/') 15 | , expectedBody = 'connected' 16 | , statusCode = 200 17 | 18 | rimraf.sync(socket) 19 | 20 | var s = http.createServer(function(req, res) { 21 | var incomingUrl = url.parse(req.url) 22 | switch (incomingUrl.pathname) { 23 | case rawPath: 24 | assert.equal(incomingUrl.pathname, rawPath, 'requested path is sent to server') 25 | break 26 | 27 | case queryPath: 28 | assert.equal(incomingUrl.pathname, queryPath, 'requested path is sent to server') 29 | assert.equal(incomingUrl.search, searchString, 'query string is sent to server') 30 | break 31 | 32 | default: 33 | assert(false, 'A valid path was requested') 34 | } 35 | res.statusCode = statusCode 36 | res.end(expectedBody) 37 | }) 38 | 39 | tape('setup', function(t) { 40 | s.listen(socket, function() { 41 | t.end() 42 | }) 43 | }) 44 | 45 | tape('unix socket connection', function(t) { 46 | request( 'http://unix:' + socket + ':' + rawPath, function(err, res, body) { 47 | t.equal(err, null, 'no error in connection') 48 | t.equal(res.statusCode, statusCode, 'got HTTP 200 OK response') 49 | t.equal(body, expectedBody, 'expected response body is received') 50 | t.end() 51 | }) 52 | }) 53 | 54 | tape('unix socket connection with qs', function(t) { 55 | request({ 56 | uri: 'http://unix:' + socket + ':' + queryPath, 57 | qs: { 58 | foo: 'bar' 59 | } 60 | }, function(err, res, body) { 61 | t.equal(err, null, 'no error in connection') 62 | t.equal(res.statusCode, statusCode, 'got HTTP 200 OK response') 63 | t.equal(body, expectedBody, 'expected response body is received') 64 | t.end() 65 | }) 66 | }) 67 | 68 | tape('cleanup', function(t) { 69 | s.close(function() { 70 | fs.unlink(socket, function() { 71 | t.end() 72 | }) 73 | }) 74 | }) 75 | -------------------------------------------------------------------------------- /lib/getProxyFromURI.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function formatHostname(hostname) { 4 | // canonicalize the hostname, so that 'oogle.com' won't match 'google.com' 5 | return hostname.replace(/^\.*/, '.').toLowerCase() 6 | } 7 | 8 | function parseNoProxyZone(zone) { 9 | zone = zone.trim().toLowerCase() 10 | 11 | var zoneParts = zone.split(':', 2) 12 | , zoneHost = formatHostname(zoneParts[0]) 13 | , zonePort = zoneParts[1] 14 | , hasPort = zone.indexOf(':') > -1 15 | 16 | return {hostname: zoneHost, port: zonePort, hasPort: hasPort} 17 | } 18 | 19 | function uriInNoProxy(uri, noProxy) { 20 | var port = uri.port || (uri.protocol === 'https:' ? '443' : '80') 21 | , hostname = formatHostname(uri.hostname) 22 | , noProxyList = noProxy.split(',') 23 | 24 | // iterate through the noProxyList until it finds a match. 25 | return noProxyList.map(parseNoProxyZone).some(function(noProxyZone) { 26 | var isMatchedAt = hostname.indexOf(noProxyZone.hostname) 27 | , hostnameMatched = ( 28 | isMatchedAt > -1 && 29 | (isMatchedAt === hostname.length - noProxyZone.hostname.length) 30 | ) 31 | 32 | if (noProxyZone.hasPort) { 33 | return (port === noProxyZone.port) && hostnameMatched 34 | } 35 | 36 | return hostnameMatched 37 | }) 38 | } 39 | 40 | function getProxyFromURI(uri) { 41 | // Decide the proper request proxy to use based on the request URI object and the 42 | // environmental variables (NO_PROXY, HTTP_PROXY, etc.) 43 | // respect NO_PROXY environment variables (see: http://lynx.isc.org/current/breakout/lynx_help/keystrokes/environments.html) 44 | 45 | var noProxy = process.env.NO_PROXY || process.env.no_proxy || '' 46 | 47 | // if the noProxy is a wildcard then return null 48 | 49 | if (noProxy === '*') { 50 | return null 51 | } 52 | 53 | // if the noProxy is not empty and the uri is found return null 54 | 55 | if (noProxy !== '' && uriInNoProxy(uri, noProxy)) { 56 | return null 57 | } 58 | 59 | // Check for HTTP or HTTPS Proxy in environment Else default to null 60 | 61 | if (uri.protocol === 'http:') { 62 | return process.env.HTTP_PROXY || 63 | process.env.http_proxy || null 64 | } 65 | 66 | if (uri.protocol === 'https:') { 67 | return process.env.HTTPS_PROXY || 68 | process.env.https_proxy || 69 | process.env.HTTP_PROXY || 70 | process.env.http_proxy || null 71 | } 72 | 73 | // if none of that works, return null 74 | // (What uri protocol are you using then?) 75 | 76 | return null 77 | } 78 | 79 | module.exports = getProxyFromURI 80 | -------------------------------------------------------------------------------- /tests/test-json-request.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var server = require('./server') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var s = server.createServer() 8 | 9 | tape('setup', function(t) { 10 | s.listen(s.port, function() { 11 | t.end() 12 | }) 13 | }) 14 | 15 | function testJSONValue(testId, value) { 16 | tape('test ' + testId, function(t) { 17 | var testUrl = '/' + testId 18 | s.on(testUrl, server.createPostJSONValidator(value, 'application/json')) 19 | var opts = { 20 | method: 'PUT', 21 | uri: s.url + testUrl, 22 | json: true, 23 | body: value 24 | } 25 | request(opts, function (err, resp, body) { 26 | t.equal(err, null) 27 | t.equal(resp.statusCode, 200) 28 | t.deepEqual(body, value) 29 | t.end() 30 | }) 31 | }) 32 | } 33 | 34 | function testJSONValueReviver(testId, value, reviver, revivedValue) { 35 | tape('test ' + testId, function(t) { 36 | var testUrl = '/' + testId 37 | s.on(testUrl, server.createPostJSONValidator(value, 'application/json')) 38 | var opts = { 39 | method: 'PUT', 40 | uri: s.url + testUrl, 41 | json: true, 42 | jsonReviver: reviver, 43 | body: value 44 | } 45 | request(opts, function (err, resp, body) { 46 | t.equal(err, null) 47 | t.equal(resp.statusCode, 200) 48 | t.deepEqual(body, revivedValue) 49 | t.end() 50 | }) 51 | }) 52 | } 53 | 54 | testJSONValue('jsonNull', null) 55 | testJSONValue('jsonTrue', true) 56 | testJSONValue('jsonFalse', false) 57 | testJSONValue('jsonNumber', -289365.2938) 58 | testJSONValue('jsonString', 'some string') 59 | testJSONValue('jsonArray', ['value1', 2, null, 8925.53289, true, false, ['array'], { object: 'property' }]) 60 | testJSONValue('jsonObject', { 61 | trueProperty: true, 62 | falseProperty: false, 63 | numberProperty: -98346.34698, 64 | stringProperty: 'string', 65 | nullProperty: null, 66 | arrayProperty: ['array'], 67 | objectProperty: { object: 'property' } 68 | }) 69 | 70 | testJSONValueReviver('jsonReviver', -48269.592, function (k, v) { 71 | return v * -1 72 | }, 48269.592) 73 | testJSONValueReviver('jsonReviverInvalid', -48269.592, 'invalid reviver', -48269.592) 74 | 75 | tape('missing body', function (t) { 76 | s.on('/missing-body', function (req, res) { 77 | t.equal(req.headers['content-type'], undefined) 78 | res.end() 79 | }) 80 | request({url:s.url + '/missing-body', json:true}, function () { 81 | t.end() 82 | }) 83 | }) 84 | 85 | tape('cleanup', function(t) { 86 | s.close(function() { 87 | t.end() 88 | }) 89 | }) 90 | -------------------------------------------------------------------------------- /tests/test-rfc3986.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | 8 | function runTest (t, options) { 9 | 10 | var server = http.createServer(function(req, res) { 11 | 12 | var data = '' 13 | req.setEncoding('utf8') 14 | 15 | req.on('data', function(d) { 16 | data += d 17 | }) 18 | 19 | req.on('end', function() { 20 | if (options.qs) { 21 | t.equal(req.url, '/?rfc3986=%21%2A%28%29%27') 22 | } 23 | t.equal(data, options._expectBody) 24 | 25 | res.writeHead(200) 26 | res.end('done') 27 | }) 28 | }) 29 | 30 | server.listen(6767, function() { 31 | 32 | request.post('http://localhost:6767', options, function(err, res, body) { 33 | t.equal(err, null) 34 | server.close(function() { 35 | t.end() 36 | }) 37 | }) 38 | }) 39 | } 40 | 41 | var bodyEscaped = 'rfc3986=%21%2A%28%29%27' 42 | , bodyJson = '{"rfc3986":"!*()\'"}' 43 | 44 | var cases = [ 45 | { 46 | _name: 'qs', 47 | qs: {rfc3986: '!*()\''}, 48 | _expectBody: '' 49 | }, 50 | { 51 | _name: 'qs + json', 52 | qs: {rfc3986: '!*()\''}, 53 | json: true, 54 | _expectBody: '' 55 | }, 56 | { 57 | _name: 'form', 58 | form: {rfc3986: '!*()\''}, 59 | _expectBody: bodyEscaped 60 | }, 61 | { 62 | _name: 'form + json', 63 | form: {rfc3986: '!*()\''}, 64 | json: true, 65 | _expectBody: bodyEscaped 66 | }, 67 | { 68 | _name: 'qs + form', 69 | qs: {rfc3986: '!*()\''}, 70 | form: {rfc3986: '!*()\''}, 71 | _expectBody: bodyEscaped 72 | }, 73 | { 74 | _name: 'qs + form + json', 75 | qs: {rfc3986: '!*()\''}, 76 | form: {rfc3986: '!*()\''}, 77 | json: true, 78 | _expectBody: bodyEscaped 79 | }, 80 | { 81 | _name: 'body + header + json', 82 | headers: {'content-type': 'application/x-www-form-urlencoded; charset=UTF-8'}, 83 | body: 'rfc3986=!*()\'', 84 | json: true, 85 | _expectBody: bodyEscaped 86 | }, 87 | { 88 | _name: 'body + json', 89 | body: {rfc3986: '!*()\''}, 90 | json: true, 91 | _expectBody: bodyJson 92 | }, 93 | { 94 | _name: 'json object', 95 | json: {rfc3986: '!*()\''}, 96 | _expectBody: bodyJson 97 | } 98 | ] 99 | 100 | var libs = ['qs', 'querystring'] 101 | 102 | libs.forEach(function (lib) { 103 | cases.forEach(function (options) { 104 | options.useQuerystring = (lib === 'querystring') 105 | tape(lib + ' rfc3986 ' + options._name, function(t) { 106 | runTest(t, options) 107 | }) 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /tests/test-params.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var server = require('./server') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var s = server.createServer() 8 | 9 | function runTest(name, test) { 10 | tape(name, function(t) { 11 | s.on('/' + name, test.resp) 12 | request(s.url + '/' + name, test, function(err, resp, body) { 13 | t.equal(err, null) 14 | if (test.expectBody) { 15 | if (Buffer.isBuffer(test.expectBody)) { 16 | t.equal(test.expectBody.toString(), body.toString()) 17 | } else { 18 | t.deepEqual(test.expectBody, body) 19 | } 20 | } 21 | t.end() 22 | }) 23 | }) 24 | } 25 | 26 | tape('setup', function(t) { 27 | s.listen(s.port, function() { 28 | t.end() 29 | }) 30 | }) 31 | 32 | runTest('testGet', { 33 | resp : server.createGetResponse('TESTING!') 34 | , expectBody: 'TESTING!' 35 | }) 36 | 37 | runTest('testGetChunkBreak', { 38 | resp : server.createChunkResponse( 39 | [ new Buffer([239]) 40 | , new Buffer([163]) 41 | , new Buffer([191]) 42 | , new Buffer([206]) 43 | , new Buffer([169]) 44 | , new Buffer([226]) 45 | , new Buffer([152]) 46 | , new Buffer([131]) 47 | ]) 48 | , expectBody: '\uf8ff\u03a9\u2603' 49 | }) 50 | 51 | runTest('testGetBuffer', { 52 | resp : server.createGetResponse(new Buffer('TESTING!')) 53 | , encoding: null 54 | , expectBody: new Buffer('TESTING!') 55 | }) 56 | 57 | runTest('testGetJSON', { 58 | resp : server.createGetResponse('{"test":true}', 'application/json') 59 | , json : true 60 | , expectBody: {'test':true} 61 | }) 62 | 63 | runTest('testPutString', { 64 | resp : server.createPostValidator('PUTTINGDATA') 65 | , method : 'PUT' 66 | , body : 'PUTTINGDATA' 67 | }) 68 | 69 | runTest('testPutBuffer', { 70 | resp : server.createPostValidator('PUTTINGDATA') 71 | , method : 'PUT' 72 | , body : new Buffer('PUTTINGDATA') 73 | }) 74 | 75 | runTest('testPutJSON', { 76 | resp : server.createPostValidator(JSON.stringify({foo: 'bar'})) 77 | , method: 'PUT' 78 | , json: {foo: 'bar'} 79 | }) 80 | 81 | runTest('testPutMultipart', { 82 | resp: server.createPostValidator( 83 | '--__BOUNDARY__\r\n' + 84 | 'content-type: text/html\r\n' + 85 | '\r\n' + 86 | 'Oh hi.' + 87 | '\r\n--__BOUNDARY__\r\n\r\n' + 88 | 'Oh hi.' + 89 | '\r\n--__BOUNDARY__--' 90 | ) 91 | , method: 'PUT' 92 | , multipart: 93 | [ {'content-type': 'text/html', 'body': 'Oh hi.'} 94 | , {'body': 'Oh hi.'} 95 | ] 96 | }) 97 | 98 | tape('cleanup', function(t) { 99 | s.close(function() { 100 | t.end() 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /tests/test-errors.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , tape = require('tape') 5 | 6 | var local = 'http://localhost:8888/asdf' 7 | 8 | tape('without uri', function(t) { 9 | t.throws(function() { 10 | request({}) 11 | }, /^Error: options\.uri is a required argument$/) 12 | t.end() 13 | }) 14 | 15 | tape('invalid uri 1', function(t) { 16 | t.throws(function() { 17 | request({ 18 | uri: 'this-is-not-a-valid-uri' 19 | }) 20 | }, /^Error: Invalid URI/) 21 | t.end() 22 | }) 23 | 24 | tape('invalid uri 2', function(t) { 25 | t.throws(function() { 26 | request({ 27 | uri: 'github.com/uri-is-not-valid-without-protocol' 28 | }) 29 | }, /^Error: Invalid URI/) 30 | t.end() 31 | }) 32 | 33 | tape('invalid uri + NO_PROXY', function(t) { 34 | process.env.NO_PROXY = 'google.com' 35 | t.throws(function() { 36 | request({ 37 | uri: 'invalid' 38 | }) 39 | }, /^Error: Invalid URI/) 40 | delete process.env.NO_PROXY 41 | t.end() 42 | }) 43 | 44 | tape('deprecated unix URL', function(t) { 45 | t.throws(function() { 46 | request({ 47 | uri: 'unix://path/to/socket/and/then/request/path' 48 | }) 49 | }, /^Error: `unix:\/\/` URL scheme is no longer supported/) 50 | t.end() 51 | }) 52 | 53 | tape('invalid body', function(t) { 54 | t.throws(function() { 55 | request({ 56 | uri: local, body: {} 57 | }) 58 | }, /^Error: Argument error, options\.body\.$/) 59 | t.end() 60 | }) 61 | 62 | tape('invalid multipart', function(t) { 63 | t.throws(function() { 64 | request({ 65 | uri: local, 66 | multipart: 'foo' 67 | }) 68 | }, /^Error: Argument error, options\.multipart\.$/) 69 | t.end() 70 | }) 71 | 72 | tape('multipart without body 1', function(t) { 73 | t.throws(function() { 74 | request({ 75 | uri: local, 76 | multipart: [ {} ] 77 | }) 78 | }, /^Error: Body attribute missing in multipart\.$/) 79 | t.end() 80 | }) 81 | 82 | tape('multipart without body 2', function(t) { 83 | t.throws(function() { 84 | request(local, { 85 | multipart: [ {} ] 86 | }) 87 | }, /^Error: Body attribute missing in multipart\.$/) 88 | t.end() 89 | }) 90 | 91 | tape('head method with a body', function(t) { 92 | t.throws(function() { 93 | request(local, { 94 | method: 'HEAD', 95 | body: 'foo' 96 | }) 97 | }, /HTTP HEAD requests MUST NOT include a request body/) 98 | t.end() 99 | }) 100 | 101 | tape('head method with a body 2', function(t) { 102 | t.throws(function() { 103 | request.head(local, { 104 | body: 'foo' 105 | }) 106 | }, /HTTP HEAD requests MUST NOT include a request body/) 107 | t.end() 108 | }) 109 | -------------------------------------------------------------------------------- /tests/squid.conf: -------------------------------------------------------------------------------- 1 | # 2 | # Recommended minimum configuration: 3 | # 4 | acl localhost src 127.0.0.1/32 ::1 5 | acl to_localhost dst 127.0.0.0/8 0.0.0.0/32 ::1 6 | 7 | # Example rule allowing access from your local networks. 8 | # Adapt to list your (internal) IP networks from where browsing 9 | # should be allowed 10 | acl localnet src 10.0.0.0/8 # RFC1918 possible internal network 11 | acl localnet src 172.16.0.0/12 # RFC1918 possible internal network 12 | acl localnet src 192.168.0.0/16 # RFC1918 possible internal network 13 | acl localnet src fc00::/7 # RFC 4193 local private network range 14 | acl localnet src fe80::/10 # RFC 4291 link-local (directly plugged) machines 15 | 16 | acl SSL_ports port 443 17 | acl Safe_ports port 80 # http 18 | acl Safe_ports port 21 # ftp 19 | acl Safe_ports port 443 # https 20 | acl Safe_ports port 70 # gopher 21 | acl Safe_ports port 210 # wais 22 | acl Safe_ports port 1025-65535 # unregistered ports 23 | acl Safe_ports port 280 # http-mgmt 24 | acl Safe_ports port 488 # gss-http 25 | acl Safe_ports port 591 # filemaker 26 | acl Safe_ports port 777 # multiling http 27 | acl CONNECT method CONNECT 28 | 29 | # 30 | # Recommended minimum Access Permission configuration: 31 | # 32 | # Only allow cachemgr access from localhost 33 | http_access allow manager localhost 34 | http_access deny manager 35 | 36 | # Deny requests to certain unsafe ports 37 | http_access deny !Safe_ports 38 | 39 | # Deny CONNECT to other than secure SSL ports 40 | #http_access deny CONNECT !SSL_ports 41 | 42 | # We strongly recommend the following be uncommented to protect innocent 43 | # web applications running on the proxy server who think the only 44 | # one who can access services on "localhost" is a local user 45 | #http_access deny to_localhost 46 | 47 | # 48 | # INSERT YOUR OWN RULE(S) HERE TO ALLOW ACCESS FROM YOUR CLIENTS 49 | # 50 | 51 | # Example rule allowing access from your local networks. 52 | # Adapt localnet in the ACL section to list your (internal) IP networks 53 | # from where browsing should be allowed 54 | http_access allow localnet 55 | http_access allow localhost 56 | 57 | # And finally deny all other access to this proxy 58 | http_access deny all 59 | 60 | # Squid normally listens to port 3128 61 | http_port 3128 62 | 63 | # We recommend you to use at least the following line. 64 | hierarchy_stoplist cgi-bin ? 65 | 66 | # Uncomment and adjust the following to add a disk cache directory. 67 | #cache_dir ufs /usr/local/var/cache 100 16 256 68 | 69 | # Leave coredumps in the first cache dir 70 | coredump_dir /usr/local/var/cache 71 | 72 | # Add any of your own refresh_pattern entries above these. 73 | refresh_pattern ^ftp: 1440 20% 10080 74 | refresh_pattern ^gopher: 1440 0% 1440 75 | refresh_pattern -i (/cgi-bin/|\?) 0 0% 0 76 | refresh_pattern . 0 20% 4320 77 | -------------------------------------------------------------------------------- /tests/test-isUrl.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var s = http.createServer(function(req, res) { 8 | res.statusCode = 200 9 | res.end('ok') 10 | }) 11 | 12 | tape('setup', function(t) { 13 | s.listen(6767, function() { 14 | t.end() 15 | }) 16 | }) 17 | 18 | tape('lowercase', function(t) { 19 | request('http://localhost:6767', function(err, resp, body) { 20 | t.equal(err, null) 21 | t.equal(body, 'ok') 22 | t.end() 23 | }) 24 | }) 25 | 26 | tape('uppercase', function(t) { 27 | request('HTTP://localhost:6767', function(err, resp, body) { 28 | t.equal(err, null) 29 | t.equal(body, 'ok') 30 | t.end() 31 | }) 32 | }) 33 | 34 | tape('mixedcase', function(t) { 35 | request('HtTp://localhost:6767', function(err, resp, body) { 36 | t.equal(err, null) 37 | t.equal(body, 'ok') 38 | t.end() 39 | }) 40 | }) 41 | 42 | tape('hostname and port', function(t) { 43 | request({ 44 | uri: { 45 | protocol: 'http:', 46 | hostname: 'localhost', 47 | port: 6767 48 | } 49 | }, function(err, res, body) { 50 | t.equal(err, null) 51 | t.equal(body, 'ok') 52 | t.end() 53 | }) 54 | }) 55 | 56 | tape('hostname and port 1', function(t) { 57 | request({ 58 | uri: { 59 | protocol: 'http:', 60 | hostname: 'localhost', 61 | port: 6767 62 | } 63 | }, function(err, res, body) { 64 | t.equal(err, null) 65 | t.equal(body, 'ok') 66 | t.end() 67 | }) 68 | }) 69 | 70 | tape('hostname and port 2', function(t) { 71 | request({ 72 | protocol: 'http:', 73 | hostname: 'localhost', 74 | port: 6767 75 | }, { 76 | // need this empty options object, otherwise request thinks no uri was set 77 | }, function(err, res, body) { 78 | t.equal(err, null) 79 | t.equal(body, 'ok') 80 | t.end() 81 | }) 82 | }) 83 | 84 | tape('hostname and port 3', function(t) { 85 | request({ 86 | protocol: 'http:', 87 | hostname: 'localhost', 88 | port: 6767 89 | }, function(err, res, body) { 90 | t.notEqual(err, null) 91 | t.equal(err.message, 'options.uri is a required argument') 92 | t.equal(body, undefined) 93 | t.end() 94 | }) 95 | }) 96 | 97 | tape('hostname and query string', function(t) { 98 | request({ 99 | uri: { 100 | protocol: 'http:', 101 | hostname: 'localhost', 102 | port: 6767 103 | }, 104 | qs: { 105 | test: 'test' 106 | } 107 | }, function(err, res, body) { 108 | t.equal(err, null) 109 | t.equal(body, 'ok') 110 | t.end() 111 | }) 112 | }) 113 | 114 | tape('cleanup', function(t) { 115 | s.close(function() { 116 | t.end() 117 | }) 118 | }) 119 | -------------------------------------------------------------------------------- /tests/test-node-debug.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , http = require('http') 5 | , tape = require('tape') 6 | 7 | var s = http.createServer(function(req, res) { 8 | res.statusCode = 200 9 | res.end('') 10 | }) 11 | 12 | var stderr = [] 13 | , prevStderrLen = 0 14 | 15 | tape('setup', function(t) { 16 | process.stderr._oldWrite = process.stderr.write 17 | process.stderr.write = function(string, encoding, fd) { 18 | stderr.push(string) 19 | } 20 | 21 | s.listen(6767, function() { 22 | t.end() 23 | }) 24 | }) 25 | 26 | tape('a simple request should not fail with debugging enabled', function(t) { 27 | request.debug = true 28 | t.equal(request.Request.debug, true, 'request.debug sets request.Request.debug') 29 | t.equal(request.debug, true, 'request.debug gets request.Request.debug') 30 | stderr = [] 31 | 32 | request('http://localhost:6767', function(err, res, body) { 33 | t.ifError(err, 'the request did not fail') 34 | t.ok(res, 'the request did not fail') 35 | 36 | t.ok(stderr.length, 'stderr has some messages') 37 | var patterns = [ 38 | /^REQUEST { uri: /, 39 | /^REQUEST make request http:\/\/localhost:6767\/\n$/, 40 | /^REQUEST onRequestResponse /, 41 | /^REQUEST finish init /, 42 | /^REQUEST response end /, 43 | /^REQUEST end event /, 44 | /^REQUEST emitting complete / 45 | ] 46 | patterns.forEach(function(pattern) { 47 | var found = false 48 | stderr.forEach(function(msg) { 49 | if (pattern.test(msg)) { 50 | found = true 51 | } 52 | }) 53 | t.ok(found, 'a log message matches ' + pattern) 54 | }) 55 | prevStderrLen = stderr.length 56 | t.end() 57 | }) 58 | }) 59 | 60 | tape('there should be no further lookups on process.env', function(t) { 61 | process.env.NODE_DEBUG = '' 62 | stderr = [] 63 | 64 | request('http://localhost:6767', function(err, res, body) { 65 | t.ifError(err, 'the request did not fail') 66 | t.ok(res, 'the request did not fail') 67 | t.equal(stderr.length, prevStderrLen, 'env.NODE_DEBUG is not retested') 68 | t.end() 69 | }) 70 | }) 71 | 72 | tape('it should be possible to disable debugging at runtime', function(t) { 73 | request.debug = false 74 | t.equal(request.Request.debug, false, 'request.debug sets request.Request.debug') 75 | t.equal(request.debug, false, 'request.debug gets request.Request.debug') 76 | stderr = [] 77 | 78 | request('http://localhost:6767', function(err, res, body) { 79 | t.ifError(err, 'the request did not fail') 80 | t.ok(res, 'the request did not fail') 81 | t.equal(stderr.length, 0, 'debugging can be disabled') 82 | t.end() 83 | }) 84 | }) 85 | 86 | tape('cleanup', function(t) { 87 | process.stderr.write = process.stderr._oldWrite 88 | delete process.stderr._oldWrite 89 | 90 | s.close(function() { 91 | t.end() 92 | }) 93 | }) 94 | -------------------------------------------------------------------------------- /tests/test-agent.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , version = require('../lib/helpers').version 5 | , http = require('http') 6 | , ForeverAgent = require('forever-agent') 7 | , tape = require('tape') 8 | 9 | var s = http.createServer(function (req, res) { 10 | res.statusCode = 200 11 | res.end() 12 | }) 13 | 14 | tape('setup', function (t) { 15 | s.listen(6767, function() { 16 | t.end() 17 | }) 18 | }) 19 | 20 | function httpAgent (t, options, req) { 21 | var r = (req || request)(options, function (_err, res, body) { 22 | 23 | t.ok(r.agent instanceof http.Agent, 'is http.Agent') 24 | t.equal(r.agent.options.keepAlive, true, 'is keepAlive') 25 | t.equal(Object.keys(r.agent.sockets).length, 1, '1 socket name') 26 | 27 | var name = (typeof r.agent.getName === 'function') 28 | ? r.agent.getName({port:6767}) 29 | : 'localhost:6767' // node 0.10- 30 | t.equal(r.agent.sockets[name].length, 1, '1 open socket') 31 | 32 | var socket = r.agent.sockets[name][0] 33 | socket.on('close', function () { 34 | t.equal(Object.keys(r.agent.sockets).length, 0, '0 open sockets') 35 | t.end() 36 | }) 37 | socket.end() 38 | }) 39 | } 40 | 41 | function foreverAgent (t, options, req) { 42 | var r = (req || request)(options, function (_err, res, body) { 43 | 44 | t.ok(r.agent instanceof ForeverAgent, 'is ForeverAgent') 45 | t.equal(Object.keys(r.agent.sockets).length, 1, '1 socket name') 46 | 47 | var name = 'localhost:6767' // node 0.10- 48 | t.equal(r.agent.sockets[name].length, 1, '1 open socket') 49 | 50 | var socket = r.agent.sockets[name][0] 51 | socket.on('close', function () { 52 | t.equal(Object.keys(r.agent.sockets[name]).length, 0, '0 open sockets') 53 | t.end() 54 | }) 55 | socket.end() 56 | }) 57 | } 58 | 59 | // http.Agent 60 | 61 | tape('options.agent', function (t) { 62 | httpAgent(t, { 63 | uri: 'http://localhost:6767', 64 | agent: new http.Agent({keepAlive: true}) 65 | }) 66 | }) 67 | 68 | tape('options.agentClass + options.agentOptions', function (t) { 69 | httpAgent(t, { 70 | uri: 'http://localhost:6767', 71 | agentClass: http.Agent, 72 | agentOptions: {keepAlive: true} 73 | }) 74 | }) 75 | 76 | // forever-agent 77 | 78 | tape('options.forever = true', function (t) { 79 | var v = version() 80 | var options = { 81 | uri: 'http://localhost:6767', 82 | forever: true 83 | } 84 | 85 | if (v.major === 0 && v.minor <= 10) {foreverAgent(t, options)} 86 | else {httpAgent(t, options)} 87 | }) 88 | 89 | tape('forever() method', function (t) { 90 | var v = version() 91 | var options = { 92 | uri: 'http://localhost:6767' 93 | } 94 | var r = request.forever({maxSockets: 1}) 95 | 96 | if (v.major === 0 && v.minor <= 10) {foreverAgent(t, options, r)} 97 | else {httpAgent(t, options, r)} 98 | }) 99 | 100 | tape('cleanup', function (t) { 101 | s.close(function() { 102 | t.end() 103 | }) 104 | }) 105 | -------------------------------------------------------------------------------- /tests/test-cookies.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | 8 | var validUrl = 'http://localhost:6767/valid' 9 | , malformedUrl = 'http://localhost:6767/malformed' 10 | , invalidUrl = 'http://localhost:6767/invalid' 11 | 12 | var server = http.createServer(function (req, res) { 13 | if (req.url === '/valid') { 14 | res.setHeader('set-cookie', 'foo=bar') 15 | } else if (req.url === '/malformed') { 16 | res.setHeader('set-cookie', 'foo') 17 | } else if (req.url === '/invalid') { 18 | res.setHeader('set-cookie', 'foo=bar; Domain=foo.com') 19 | } 20 | res.end('okay') 21 | }) 22 | 23 | tape('setup', function(t) { 24 | server.listen(6767, function() { 25 | t.end() 26 | }) 27 | }) 28 | 29 | tape('simple cookie creation', function(t) { 30 | var cookie = request.cookie('foo=bar') 31 | t.equals(cookie.key, 'foo') 32 | t.equals(cookie.value, 'bar') 33 | t.end() 34 | }) 35 | 36 | tape('simple malformed cookie creation', function(t) { 37 | var cookie = request.cookie('foo') 38 | t.equals(cookie.key, '') 39 | t.equals(cookie.value, 'foo') 40 | t.end() 41 | }) 42 | 43 | tape('after server sends a cookie', function(t) { 44 | var jar1 = request.jar() 45 | request({ 46 | method: 'GET', 47 | url: validUrl, 48 | jar: jar1 49 | }, 50 | function (error, response, body) { 51 | t.equal(error, null) 52 | t.equal(jar1.getCookieString(validUrl), 'foo=bar') 53 | t.equal(body, 'okay') 54 | 55 | var cookies = jar1.getCookies(validUrl) 56 | t.equal(cookies.length, 1) 57 | t.equal(cookies[0].key, 'foo') 58 | t.equal(cookies[0].value, 'bar') 59 | t.end() 60 | }) 61 | }) 62 | 63 | tape('after server sends a malformed cookie', function(t) { 64 | var jar = request.jar() 65 | request({ 66 | method: 'GET', 67 | url: malformedUrl, 68 | jar: jar 69 | }, 70 | function (error, response, body) { 71 | t.equal(error, null) 72 | t.equal(jar.getCookieString(malformedUrl), 'foo') 73 | t.equal(body, 'okay') 74 | 75 | var cookies = jar.getCookies(malformedUrl) 76 | t.equal(cookies.length, 1) 77 | t.equal(cookies[0].key, '') 78 | t.equal(cookies[0].value, 'foo') 79 | t.end() 80 | }) 81 | }) 82 | 83 | tape('after server sends a cookie for a different domain', function(t) { 84 | var jar2 = request.jar() 85 | request({ 86 | method: 'GET', 87 | url: invalidUrl, 88 | jar: jar2 89 | }, 90 | function (error, response, body) { 91 | t.equal(error, null) 92 | t.equal(jar2.getCookieString(validUrl), '') 93 | t.deepEqual(jar2.getCookies(validUrl), []) 94 | t.equal(body, 'okay') 95 | t.end() 96 | }) 97 | }) 98 | 99 | tape('make sure setCookie works', function(t) { 100 | var jar3 = request.jar() 101 | , err = null 102 | try { 103 | jar3.setCookie(request.cookie('foo=bar'), validUrl) 104 | } catch (e) { 105 | err = e 106 | } 107 | t.equal(err, null) 108 | var cookies = jar3.getCookies(validUrl) 109 | t.equal(cookies.length, 1) 110 | t.equal(cookies[0].key, 'foo') 111 | t.equal(cookies[0].value, 'bar') 112 | t.end() 113 | }) 114 | 115 | tape('custom store', function(t) { 116 | var Store = function() {} 117 | var store = new Store() 118 | var jar = request.jar(store) 119 | t.equals(store, jar._jar.store) 120 | t.end() 121 | }) 122 | 123 | tape('cleanup', function(t) { 124 | server.close(function() { 125 | t.end() 126 | }) 127 | }) 128 | -------------------------------------------------------------------------------- /tests/test-form.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , path = require('path') 5 | , mime = require('mime-types') 6 | , request = require('../index') 7 | , fs = require('fs') 8 | , tape = require('tape') 9 | 10 | tape('multipart form append', function(t) { 11 | 12 | var remoteFile = path.join(__dirname, 'googledoodle.jpg') 13 | , localFile = path.join(__dirname, 'unicycle.jpg') 14 | , totalLength = null 15 | , FIELDS = [] 16 | 17 | var server = http.createServer(function(req, res) { 18 | if (req.url === '/file') { 19 | res.writeHead(200, {'content-type': 'image/jpg', 'content-length':7187}) 20 | res.end(fs.readFileSync(remoteFile), 'binary') 21 | return 22 | } 23 | 24 | t.ok(/multipart\/form-data; boundary=--------------------------\d+/ 25 | .test(req.headers['content-type'])) 26 | 27 | // temp workaround 28 | var data = '' 29 | req.setEncoding('utf8') 30 | 31 | req.on('data', function(d) { 32 | data += d 33 | }) 34 | 35 | req.on('end', function() { 36 | var field 37 | // check for the fields' traces 38 | 39 | // 1st field : my_field 40 | field = FIELDS.shift() 41 | t.ok( data.indexOf('form-data; name="' + field.name + '"') !== -1 ) 42 | t.ok( data.indexOf(field.value) !== -1 ) 43 | 44 | // 2nd field : my_buffer 45 | field = FIELDS.shift() 46 | t.ok( data.indexOf('form-data; name="' + field.name + '"') !== -1 ) 47 | t.ok( data.indexOf(field.value) !== -1 ) 48 | 49 | // 3rd field : my_file 50 | field = FIELDS.shift() 51 | t.ok( data.indexOf('form-data; name="' + field.name + '"') !== -1 ) 52 | t.ok( data.indexOf('; filename="' + path.basename(field.value.path) + '"') !== -1 ) 53 | // check for unicycle.jpg traces 54 | t.ok( data.indexOf('2005:06:21 01:44:12') !== -1 ) 55 | t.ok( data.indexOf('Content-Type: ' + mime.lookup(field.value.path) ) !== -1 ) 56 | 57 | // 4th field : remote_file 58 | field = FIELDS.shift() 59 | t.ok( data.indexOf('form-data; name="' + field.name + '"') !== -1 ) 60 | t.ok( data.indexOf('; filename="' + path.basename(field.value.path) + '"') !== -1 ) 61 | // check for http://localhost:6767/file traces 62 | t.ok( data.indexOf('Photoshop ICC') !== -1 ) 63 | t.ok( data.indexOf('Content-Type: ' + mime.lookup(remoteFile) ) !== -1 ) 64 | 65 | t.ok( +req.headers['content-length'] === totalLength ) 66 | 67 | res.writeHead(200) 68 | res.end('done') 69 | 70 | t.equal(FIELDS.length, 0) 71 | }) 72 | }) 73 | 74 | server.listen(6767, function() { 75 | 76 | FIELDS = [ 77 | { name: 'my_field', value: 'my_value' }, 78 | { name: 'my_buffer', value: new Buffer([1, 2, 3]) }, 79 | { name: 'my_file', value: fs.createReadStream(localFile) }, 80 | { name: 'remote_file', value: request('http://localhost:6767/file') } 81 | ] 82 | 83 | var req = request.post('http://localhost:6767/upload', function(err, res, body) { 84 | t.equal(err, null) 85 | t.equal(res.statusCode, 200) 86 | t.equal(body, 'done') 87 | server.close(function() { 88 | t.end() 89 | }) 90 | }) 91 | var form = req.form() 92 | 93 | FIELDS.forEach(function(field) { 94 | form.append(field.name, field.value) 95 | }) 96 | 97 | form.getLength(function(err, length) { 98 | t.equal(err, null) 99 | totalLength = length 100 | }) 101 | }) 102 | }) 103 | -------------------------------------------------------------------------------- /tests/test-httpModule.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , https = require('https') 5 | , server = require('./server') 6 | , request = require('../index') 7 | , tape = require('tape') 8 | 9 | var faux_requests_made 10 | 11 | function clear_faux_requests() { 12 | faux_requests_made = { http: 0, https: 0 } 13 | } 14 | 15 | function wrap_request(name, module) { 16 | // Just like the http or https module, but note when a request is made. 17 | var wrapped = {} 18 | Object.keys(module).forEach(function(key) { 19 | var value = module[key] 20 | 21 | if (key === 'request') { 22 | wrapped[key] = function(/*options, callback*/) { 23 | faux_requests_made[name] += 1 24 | return value.apply(this, arguments) 25 | } 26 | } else { 27 | wrapped[key] = value 28 | } 29 | }) 30 | 31 | return wrapped 32 | } 33 | 34 | var faux_http = wrap_request('http', http) 35 | , faux_https = wrap_request('https', https) 36 | , plain_server = server.createServer() 37 | , https_server = server.createSSLServer() 38 | 39 | tape('setup', function(t) { 40 | plain_server.listen(plain_server.port, function() { 41 | plain_server.on('/plain', function (req, res) { 42 | res.writeHead(200) 43 | res.end('plain') 44 | }) 45 | plain_server.on('/to_https', function (req, res) { 46 | res.writeHead(301, { 'location': 'https://localhost:' + https_server.port + '/https' }) 47 | res.end() 48 | }) 49 | 50 | https_server.listen(https_server.port, function() { 51 | https_server.on('/https', function (req, res) { 52 | res.writeHead(200) 53 | res.end('https') 54 | }) 55 | https_server.on('/to_plain', function (req, res) { 56 | res.writeHead(302, { 'location': 'http://localhost:' + plain_server.port + '/plain' }) 57 | res.end() 58 | }) 59 | 60 | t.end() 61 | }) 62 | }) 63 | }) 64 | 65 | function run_tests(name, httpModules) { 66 | tape(name, function(t) { 67 | var to_https = 'http://localhost:' + plain_server.port + '/to_https' 68 | , to_plain = 'https://localhost:' + https_server.port + '/to_plain' 69 | , options = { httpModules: httpModules, strictSSL: false } 70 | , modulesTest = httpModules || {} 71 | 72 | clear_faux_requests() 73 | 74 | request(to_https, options, function (err, res, body) { 75 | t.equal(err, null) 76 | t.equal(res.statusCode, 200) 77 | t.equal(body, 'https', 'Received HTTPS server body') 78 | 79 | t.equal(faux_requests_made.http, modulesTest['http:' ] ? 1 : 0) 80 | t.equal(faux_requests_made.https, modulesTest['https:'] ? 1 : 0) 81 | 82 | request(to_plain, options, function (err, res, body) { 83 | t.equal(err, null) 84 | t.equal(res.statusCode, 200) 85 | t.equal(body, 'plain', 'Received HTTPS server body') 86 | 87 | t.equal(faux_requests_made.http, modulesTest['http:' ] ? 2 : 0) 88 | t.equal(faux_requests_made.https, modulesTest['https:'] ? 2 : 0) 89 | 90 | t.end() 91 | }) 92 | }) 93 | }) 94 | } 95 | 96 | run_tests('undefined') 97 | run_tests('empty', {}) 98 | run_tests('http only', { 'http:': faux_http }) 99 | run_tests('https only', { 'https:': faux_https }) 100 | run_tests('http and https', { 'http:': faux_http, 'https:': faux_https }) 101 | 102 | tape('cleanup', function(t) { 103 | plain_server.close(function() { 104 | https_server.close(function() { 105 | t.end() 106 | }) 107 | }) 108 | }) 109 | -------------------------------------------------------------------------------- /tests/fixtures/har.json: -------------------------------------------------------------------------------- 1 | { 2 | "application-form-encoded": { 3 | "method": "POST", 4 | "headers": [ 5 | { 6 | "name": "content-type", 7 | "value": "application/x-www-form-urlencoded" 8 | } 9 | ], 10 | "postData": { 11 | "mimeType": "application/x-www-form-urlencoded; charset=UTF-8", 12 | "params": [ 13 | { 14 | "name": "foo", 15 | "value": "bar" 16 | }, 17 | { 18 | "name": "hello", 19 | "value": "world" 20 | } 21 | ] 22 | } 23 | }, 24 | 25 | "application-json": { 26 | "method": "POST", 27 | "headers": [ 28 | { 29 | "name": "content-type", 30 | "value": "application/json" 31 | } 32 | ], 33 | "postData": { 34 | "mimeType": "application/json", 35 | "text": "{\"number\":1,\"string\":\"f\\\"oo\",\"arr\":[1,2,3],\"nested\":{\"a\":\"b\"},\"arr_mix\":[1,\"a\",{\"arr_mix_nested\":{}}]}" 36 | } 37 | }, 38 | 39 | "cookies": { 40 | "method": "POST", 41 | "cookies": [ 42 | { 43 | "name": "foo", 44 | "value": "bar" 45 | }, 46 | { 47 | "name": "bar", 48 | "value": "baz" 49 | } 50 | ] 51 | }, 52 | 53 | "custom-method": { 54 | "method": "PROPFIND" 55 | }, 56 | 57 | "headers": { 58 | "method": "GET", 59 | "headers": [ 60 | { 61 | "name": "x-foo", 62 | "value": "Bar" 63 | } 64 | ] 65 | }, 66 | 67 | "multipart-data": { 68 | "method": "POST", 69 | "headers": [ 70 | { 71 | "name": "content-type", 72 | "value": "multipart/form-data" 73 | } 74 | ], 75 | "postData": { 76 | "mimeType": "multipart/form-data", 77 | "params": [ 78 | { 79 | "name": "foo", 80 | "value": "Hello World", 81 | "fileName": "hello.txt", 82 | "contentType": "text/plain" 83 | } 84 | ] 85 | } 86 | }, 87 | 88 | "multipart-file": { 89 | "method": "POST", 90 | "headers": [ 91 | { 92 | "name": "content-type", 93 | "value": "multipart/form-data" 94 | } 95 | ], 96 | "postData": { 97 | "mimeType": "multipart/form-data", 98 | "params": [ 99 | { 100 | "name": "foo", 101 | "fileName": "../tests/unicycle.jpg", 102 | "contentType": "image/jpeg" 103 | } 104 | ] 105 | } 106 | }, 107 | 108 | "multipart-form-data": { 109 | "method": "POST", 110 | "headers": [ 111 | { 112 | "name": "content-type", 113 | "value": "multipart/form-data" 114 | } 115 | ], 116 | "postData": { 117 | "mimeType": "multipart/form-data", 118 | "params": [ 119 | { 120 | "name": "foo", 121 | "value": "bar" 122 | } 123 | ] 124 | } 125 | }, 126 | 127 | "query": { 128 | "method": "GET", 129 | "queryString": [ 130 | { 131 | "name": "foo", 132 | "value": "bar" 133 | }, 134 | { 135 | "name": "foo", 136 | "value": "baz" 137 | }, 138 | { 139 | "name": "baz", 140 | "value": "abc" 141 | } 142 | ] 143 | }, 144 | 145 | "text-plain": { 146 | "method": "POST", 147 | "headers": [ 148 | { 149 | "name": "content-type", 150 | "value": "text/plain" 151 | } 152 | ], 153 | "postData": { 154 | "mimeType": "text/plain", 155 | "text": "Hello World" 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /tests/test-https.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | // a test where we validate the siguature of the keys 4 | // otherwise exactly the same as the ssl test 5 | 6 | var server = require('./server') 7 | , request = require('../index') 8 | , fs = require('fs') 9 | , path = require('path') 10 | , tape = require('tape') 11 | 12 | var s = server.createSSLServer() 13 | , caFile = path.resolve(__dirname, 'ssl/ca/ca.crt') 14 | , ca = fs.readFileSync(caFile) 15 | , opts = { 16 | ciphers: 'AES256-SHA', 17 | key: path.resolve(__dirname, 'ssl/ca/server.key'), 18 | cert: path.resolve(__dirname, 'ssl/ca/server.crt') 19 | } 20 | , sStrict = server.createSSLServer(s.port + 1, opts) 21 | 22 | function runAllTests(strict, s) { 23 | var strictMsg = (strict ? 'strict ' : 'relaxed ') 24 | 25 | tape(strictMsg + 'setup', function(t) { 26 | s.listen(s.port, function() { 27 | t.end() 28 | }) 29 | }) 30 | 31 | function runTest(name, test) { 32 | tape(strictMsg + name, function(t) { 33 | s.on('/' + name, test.resp) 34 | test.uri = s.url + '/' + name 35 | if (strict) { 36 | test.strictSSL = true 37 | test.ca = ca 38 | test.headers = { host: 'testing.request.mikealrogers.com' } 39 | } else { 40 | test.rejectUnauthorized = false 41 | } 42 | request(test, function(err, resp, body) { 43 | t.equal(err, null) 44 | if (test.expectBody) { 45 | t.deepEqual(test.expectBody, body) 46 | } 47 | t.end() 48 | }) 49 | }) 50 | } 51 | 52 | runTest('testGet', { 53 | resp : server.createGetResponse('TESTING!') 54 | , expectBody: 'TESTING!' 55 | }) 56 | 57 | runTest('testGetChunkBreak', { 58 | resp : server.createChunkResponse( 59 | [ new Buffer([239]) 60 | , new Buffer([163]) 61 | , new Buffer([191]) 62 | , new Buffer([206]) 63 | , new Buffer([169]) 64 | , new Buffer([226]) 65 | , new Buffer([152]) 66 | , new Buffer([131]) 67 | ]) 68 | , expectBody: '\uf8ff\u03a9\u2603' 69 | }) 70 | 71 | runTest('testGetJSON', { 72 | resp : server.createGetResponse('{"test":true}', 'application/json') 73 | , json : true 74 | , expectBody: {'test':true} 75 | }) 76 | 77 | runTest('testPutString', { 78 | resp : server.createPostValidator('PUTTINGDATA') 79 | , method : 'PUT' 80 | , body : 'PUTTINGDATA' 81 | }) 82 | 83 | runTest('testPutBuffer', { 84 | resp : server.createPostValidator('PUTTINGDATA') 85 | , method : 'PUT' 86 | , body : new Buffer('PUTTINGDATA') 87 | }) 88 | 89 | runTest('testPutJSON', { 90 | resp : server.createPostValidator(JSON.stringify({foo: 'bar'})) 91 | , method: 'PUT' 92 | , json: {foo: 'bar'} 93 | }) 94 | 95 | runTest('testPutMultipart', { 96 | resp: server.createPostValidator( 97 | '--__BOUNDARY__\r\n' + 98 | 'content-type: text/html\r\n' + 99 | '\r\n' + 100 | 'Oh hi.' + 101 | '\r\n--__BOUNDARY__\r\n\r\n' + 102 | 'Oh hi.' + 103 | '\r\n--__BOUNDARY__--' 104 | ) 105 | , method: 'PUT' 106 | , multipart: 107 | [ {'content-type': 'text/html', 'body': 'Oh hi.'} 108 | , {'body': 'Oh hi.'} 109 | ] 110 | }) 111 | 112 | tape(strictMsg + 'cleanup', function(t) { 113 | s.close(function() { 114 | t.end() 115 | }) 116 | }) 117 | } 118 | 119 | runAllTests(false, s) 120 | 121 | if (!process.env.running_under_istanbul) { 122 | // somehow this test modifies the process state 123 | // test coverage runs all tests in a single process via tape 124 | // executing this test causes one of the tests in test-tunnel.js to throw 125 | runAllTests(true, sStrict) 126 | } 127 | -------------------------------------------------------------------------------- /tests/test-redirect-auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var server = require('./server') 4 | , request = require('../index') 5 | , util = require('util') 6 | , tape = require('tape') 7 | 8 | var s = server.createServer() 9 | , ss = server.createSSLServer() 10 | 11 | // always send basic auth and allow non-strict SSL 12 | request = request.defaults({ 13 | auth : { 14 | user : 'test', 15 | pass : 'testing' 16 | }, 17 | rejectUnauthorized : false 18 | }) 19 | 20 | // redirect.from(proto, host).to(proto, host) returns an object with keys: 21 | // src : source URL 22 | // dst : destination URL 23 | var redirect = { 24 | from : function(fromProto, fromHost) { 25 | return { 26 | to : function(toProto, toHost) { 27 | var fromPort = (fromProto === 'http' ? s.port : ss.port) 28 | , toPort = (toProto === 'http' ? s.port : ss.port) 29 | return { 30 | src : util.format( 31 | '%s://%s:%d/to/%s/%s', 32 | fromProto, fromHost, fromPort, toProto, toHost), 33 | dst : util.format( 34 | '%s://%s:%d/from/%s/%s', 35 | toProto, toHost, toPort, fromProto, fromHost) 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | function handleRequests(srv) { 43 | ['http', 'https'].forEach(function(proto) { 44 | ['localhost', '127.0.0.1'].forEach(function(host) { 45 | srv.on(util.format('/to/%s/%s', proto, host), function(req, res) { 46 | var r = redirect 47 | .from(srv.protocol, req.headers.host.split(':')[0]) 48 | .to(proto, host) 49 | res.writeHead(301, { 50 | location : r.dst 51 | }) 52 | res.end() 53 | }) 54 | 55 | srv.on(util.format('/from/%s/%s', proto, host), function(req, res) { 56 | res.end('auth: ' + (req.headers.authorization || '(nothing)')) 57 | }) 58 | }) 59 | }) 60 | } 61 | 62 | handleRequests(s) 63 | handleRequests(ss) 64 | 65 | tape('setup', function(t) { 66 | s.listen(s.port, function() { 67 | ss.listen(ss.port, function() { 68 | t.end() 69 | }) 70 | }) 71 | }) 72 | 73 | tape('redirect URL helper', function(t) { 74 | t.deepEqual( 75 | redirect.from('http', 'localhost').to('https', '127.0.0.1'), 76 | { 77 | src : util.format('http://localhost:%d/to/https/127.0.0.1', s.port), 78 | dst : util.format('https://127.0.0.1:%d/from/http/localhost', ss.port) 79 | }) 80 | t.deepEqual( 81 | redirect.from('https', 'localhost').to('http', 'localhost'), 82 | { 83 | src : util.format('https://localhost:%d/to/http/localhost', ss.port), 84 | dst : util.format('http://localhost:%d/from/https/localhost', s.port) 85 | }) 86 | t.end() 87 | }) 88 | 89 | function runTest(name, redir, expectAuth) { 90 | tape('redirect to ' + name, function(t) { 91 | request(redir.src, function(err, res, body) { 92 | t.equal(err, null) 93 | t.equal(res.request.uri.href, redir.dst) 94 | t.equal(res.statusCode, 200) 95 | t.equal(body, expectAuth 96 | ? 'auth: Basic dGVzdDp0ZXN0aW5n' 97 | : 'auth: (nothing)') 98 | t.end() 99 | }) 100 | }) 101 | } 102 | 103 | runTest('same host and protocol', 104 | redirect.from('http', 'localhost').to('http', 'localhost'), 105 | true) 106 | 107 | runTest('same host different protocol', 108 | redirect.from('http', 'localhost').to('https', 'localhost'), 109 | true) 110 | 111 | runTest('different host same protocol', 112 | redirect.from('https', '127.0.0.1').to('https', 'localhost'), 113 | false) 114 | 115 | runTest('different host and protocol', 116 | redirect.from('http', 'localhost').to('https', '127.0.0.1'), 117 | false) 118 | 119 | tape('cleanup', function(t) { 120 | s.close(function() { 121 | ss.close(function() { 122 | t.end() 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /tests/test-multipart.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , path = require('path') 5 | , request = require('../index') 6 | , fs = require('fs') 7 | , tape = require('tape') 8 | 9 | 10 | function runTest(t, a) { 11 | var remoteFile = path.join(__dirname, 'googledoodle.jpg') 12 | , localFile = path.join(__dirname, 'unicycle.jpg') 13 | , multipartData = [] 14 | 15 | var server = http.createServer(function(req, res) { 16 | if (req.url === '/file') { 17 | res.writeHead(200, {'content-type': 'image/jpg'}) 18 | res.end(fs.readFileSync(remoteFile), 'binary') 19 | return 20 | } 21 | 22 | if (a.header) { 23 | if (a.header.indexOf('mixed') !== -1) { 24 | t.ok(req.headers['content-type'].match(/^multipart\/mixed; boundary=[^\s;]+$/)) 25 | } else { 26 | t.ok(req.headers['content-type'] 27 | .match(/^multipart\/related; boundary=XXX; type=text\/xml; start=""$/)) 28 | } 29 | } else { 30 | t.ok(req.headers['content-type'].match(/^multipart\/related; boundary=[^\s;]+$/)) 31 | } 32 | 33 | // temp workaround 34 | var data = '' 35 | req.setEncoding('utf8') 36 | 37 | req.on('data', function(d) { 38 | data += d 39 | }) 40 | 41 | req.on('end', function() { 42 | // check for the fields traces 43 | 44 | // 1st field : my_field 45 | t.ok(data.indexOf('name: my_field') !== -1) 46 | t.ok(data.indexOf(multipartData[0].body) !== -1) 47 | 48 | // 2nd field : my_buffer 49 | t.ok(data.indexOf('name: my_buffer') !== -1) 50 | t.ok(data.indexOf(multipartData[1].body) !== -1) 51 | 52 | // 3rd field : my_file 53 | t.ok(data.indexOf('name: my_file') !== -1) 54 | // check for unicycle.jpg traces 55 | t.ok(data.indexOf('2005:06:21 01:44:12') !== -1) 56 | 57 | // 4th field : remote_file 58 | t.ok(data.indexOf('name: remote_file') !== -1) 59 | // check for http://localhost:6767/file traces 60 | t.ok(data.indexOf('Photoshop ICC') !== -1) 61 | 62 | if (a.header && a.header.indexOf('boundary=XXX') !== -1) { 63 | t.ok(data.indexOf('--XXX') !== -1) 64 | } 65 | 66 | res.writeHead(200) 67 | res.end(a.json ? JSON.stringify({status: 'done'}) : 'done') 68 | }) 69 | }) 70 | 71 | server.listen(6767, function() { 72 | 73 | // @NOTE: multipartData properties must be set here so that my_file read stream does not leak in node v0.8 74 | multipartData = [ 75 | {name: 'my_field', body: 'my_value'}, 76 | {name: 'my_buffer', body: new Buffer([1, 2, 3])}, 77 | {name: 'my_file', body: fs.createReadStream(localFile)}, 78 | {name: 'remote_file', body: request('http://localhost:6767/file')} 79 | ] 80 | 81 | var reqOptions = { 82 | url: 'http://localhost:6767/upload', 83 | multipart: multipartData 84 | } 85 | if (a.header) { 86 | reqOptions.headers = { 87 | 'content-type': a.header 88 | } 89 | } 90 | if (a.json) { 91 | reqOptions.json = true 92 | } 93 | request[a.method](reqOptions, function (err, res, body) { 94 | t.equal(err, null) 95 | t.equal(res.statusCode, 200) 96 | t.deepEqual(body, a.json ? {status: 'done'} : 'done') 97 | server.close(function() { 98 | t.end() 99 | }) 100 | }) 101 | }) 102 | } 103 | 104 | var testHeaders = [ 105 | null, 106 | 'multipart/mixed', 107 | 'multipart/related; boundary=XXX; type=text/xml; start=""' 108 | ] 109 | 110 | var methods = ['post', 'get'] 111 | methods.forEach(function(method) { 112 | testHeaders.forEach(function(header) { 113 | [true, false].forEach(function(json) { 114 | var name = [ 115 | 'multipart-related', method.toUpperCase(), 116 | (header || 'default'), 117 | (json ? '+' : '-') + 'json' 118 | ].join(' ') 119 | 120 | tape(name, function(t) { 121 | runTest(t, {method: method, header: header, json: json}) 122 | }) 123 | }) 124 | }) 125 | }) 126 | -------------------------------------------------------------------------------- /tests/test-timeout.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | function checkErrCode(t, err) { 4 | t.notEqual(err, null) 5 | t.ok(err.code === 'ETIMEDOUT' || err.code === 'ESOCKETTIMEDOUT', 6 | 'Error ETIMEDOUT or ESOCKETTIMEDOUT') 7 | } 8 | 9 | if (process.env.TRAVIS === 'true') { 10 | console.error('This test is unreliable on Travis; skipping.') 11 | /*eslint no-process-exit:0*/ 12 | } else { 13 | var server = require('./server') 14 | , request = require('../index') 15 | , tape = require('tape') 16 | 17 | var s = server.createServer() 18 | 19 | // Request that waits for 200ms 20 | s.on('/timeout', function(req, res) { 21 | setTimeout(function() { 22 | res.writeHead(200, {'content-type':'text/plain'}) 23 | res.write('waited') 24 | res.end() 25 | }, 200) 26 | }) 27 | 28 | tape('setup', function(t) { 29 | s.listen(s.port, function() { 30 | t.end() 31 | }) 32 | }) 33 | 34 | tape('should timeout', function(t) { 35 | var shouldTimeout = { 36 | url: s.url + '/timeout', 37 | timeout: 100 38 | } 39 | 40 | request(shouldTimeout, function(err, res, body) { 41 | checkErrCode(t, err) 42 | t.end() 43 | }) 44 | }) 45 | 46 | tape('should set connect to false', function(t) { 47 | var shouldTimeout = { 48 | url: s.url + '/timeout', 49 | timeout: 100 50 | } 51 | 52 | request(shouldTimeout, function(err, res, body) { 53 | checkErrCode(t, err) 54 | t.ok(err.connect === false, 'Read Timeout Error should set \'connect\' property to false') 55 | t.end() 56 | }) 57 | }) 58 | 59 | tape('should timeout with events', function(t) { 60 | t.plan(3) 61 | 62 | var shouldTimeoutWithEvents = { 63 | url: s.url + '/timeout', 64 | timeout: 100 65 | } 66 | 67 | var eventsEmitted = 0 68 | request(shouldTimeoutWithEvents) 69 | .on('error', function(err) { 70 | eventsEmitted++ 71 | t.equal(1, eventsEmitted) 72 | checkErrCode(t, err) 73 | }) 74 | }) 75 | 76 | tape('should not timeout', function(t) { 77 | var shouldntTimeout = { 78 | url: s.url + '/timeout', 79 | timeout: 1200 80 | } 81 | 82 | request(shouldntTimeout, function(err, res, body) { 83 | t.equal(err, null) 84 | t.equal(body, 'waited') 85 | t.end() 86 | }) 87 | }) 88 | 89 | tape('no timeout', function(t) { 90 | var noTimeout = { 91 | url: s.url + '/timeout' 92 | } 93 | 94 | request(noTimeout, function(err, res, body) { 95 | t.equal(err, null) 96 | t.equal(body, 'waited') 97 | t.end() 98 | }) 99 | }) 100 | 101 | tape('negative timeout', function(t) { // should be treated a zero or the minimum delay 102 | var negativeTimeout = { 103 | url: s.url + '/timeout', 104 | timeout: -1000 105 | } 106 | 107 | request(negativeTimeout, function(err, res, body) { 108 | checkErrCode(t, err) 109 | t.end() 110 | }) 111 | }) 112 | 113 | tape('float timeout', function(t) { // should be rounded by setTimeout anyway 114 | var floatTimeout = { 115 | url: s.url + '/timeout', 116 | timeout: 100.76 117 | } 118 | 119 | request(floatTimeout, function(err, res, body) { 120 | checkErrCode(t, err) 121 | t.end() 122 | }) 123 | }) 124 | 125 | tape('connect timeout', function(t) { 126 | // We need a destination that will not immediately return a TCP Reset 127 | // packet. StackOverflow suggests this host: 128 | // https://stackoverflow.com/a/904609/329700 129 | var tarpitHost = 'http://10.255.255.1' 130 | var shouldConnectTimeout = { 131 | url: tarpitHost + '/timeout', 132 | timeout: 100 133 | } 134 | request(shouldConnectTimeout, function(err) { 135 | checkErrCode(t, err) 136 | t.ok(err.connect === true, 'Connect Timeout Error should set \'connect\' property to true') 137 | t.end() 138 | }) 139 | }) 140 | 141 | tape('cleanup', function(t) { 142 | s.close(function() { 143 | t.end() 144 | }) 145 | }) 146 | } 147 | -------------------------------------------------------------------------------- /tests/test-pool.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , http = require('http') 5 | , tape = require('tape') 6 | 7 | var s = http.createServer(function (req, res) { 8 | res.statusCode = 200 9 | res.end('asdf') 10 | }) 11 | 12 | tape('setup', function(t) { 13 | s.listen(6767, function() { 14 | t.end() 15 | }) 16 | }) 17 | 18 | tape('pool', function(t) { 19 | request({ 20 | url: 'http://localhost:6767', 21 | pool: false 22 | }, function(err, res, body) { 23 | t.equal(err, null) 24 | t.equal(res.statusCode, 200) 25 | t.equal(body, 'asdf') 26 | 27 | var agent = res.request.agent 28 | t.equal(agent, false) 29 | t.end() 30 | }) 31 | }) 32 | 33 | tape('forever', function(t) { 34 | var r = request({ 35 | url: 'http://localhost:6767', 36 | forever: true, 37 | pool: {maxSockets: 1024} 38 | }, function(err, res, body) { 39 | // explicitly shut down the agent 40 | if (r.agent.destroy === typeof 'function') { 41 | r.agent.destroy() 42 | } else { 43 | // node < 0.12 44 | Object.keys(r.agent.sockets).forEach(function (name) { 45 | r.agent.sockets[name].forEach(function (socket) { 46 | socket.end() 47 | }) 48 | }) 49 | } 50 | 51 | t.equal(err, null) 52 | t.equal(res.statusCode, 200) 53 | t.equal(body, 'asdf') 54 | 55 | var agent = res.request.agent 56 | t.equal(agent.maxSockets, 1024) 57 | t.end() 58 | }) 59 | }) 60 | 61 | tape('forever, should use same agent in sequential requests', function(t) { 62 | var r = request.defaults({ 63 | forever: true 64 | }) 65 | var req1 = r('http://localhost:6767') 66 | var req2 = r('http://localhost:6767/somepath') 67 | req1.abort() 68 | req2.abort() 69 | if (typeof req1.agent.destroy === 'function') { 70 | req1.agent.destroy() 71 | } 72 | if (typeof req2.agent.destroy === 'function') { 73 | req2.agent.destroy() 74 | } 75 | t.equal(req1.agent, req2.agent) 76 | t.end() 77 | }) 78 | 79 | tape('forever, should use same agent in sequential requests(with pool.maxSockets)', function(t) { 80 | var r = request.defaults({ 81 | forever: true, 82 | pool: {maxSockets: 1024} 83 | }) 84 | var req1 = r('http://localhost:6767') 85 | var req2 = r('http://localhost:6767/somepath') 86 | req1.abort() 87 | req2.abort() 88 | if (typeof req1.agent.destroy === 'function') { 89 | req1.agent.destroy() 90 | } 91 | if (typeof req2.agent.destroy === 'function') { 92 | req2.agent.destroy() 93 | } 94 | t.equal(req1.agent.maxSockets, 1024) 95 | t.equal(req1.agent, req2.agent) 96 | t.end() 97 | }) 98 | 99 | tape('forever, should use same agent in request() and request.verb', function(t) { 100 | var r = request.defaults({ 101 | forever: true, 102 | pool: {maxSockets: 1024} 103 | }) 104 | var req1 = r('http://localhost:6767') 105 | var req2 = r.get('http://localhost:6767') 106 | req1.abort() 107 | req2.abort() 108 | if (typeof req1.agent.destroy === 'function') { 109 | req1.agent.destroy() 110 | } 111 | if (typeof req2.agent.destroy === 'function') { 112 | req2.agent.destroy() 113 | } 114 | t.equal(req1.agent.maxSockets, 1024) 115 | t.equal(req1.agent, req2.agent) 116 | t.end() 117 | }) 118 | 119 | tape('should use different agent if pool option specified', function(t) { 120 | var r = request.defaults({ 121 | forever: true, 122 | pool: {maxSockets: 1024} 123 | }) 124 | var req1 = r('http://localhost:6767') 125 | var req2 = r.get({ 126 | url: 'http://localhost:6767', 127 | pool: {maxSockets: 20} 128 | }) 129 | req1.abort() 130 | req2.abort() 131 | if (typeof req1.agent.destroy === 'function') { 132 | req1.agent.destroy() 133 | } 134 | if (typeof req2.agent.destroy === 'function') { 135 | req2.agent.destroy() 136 | } 137 | t.equal(req1.agent.maxSockets, 1024) 138 | t.equal(req2.agent.maxSockets, 20) 139 | t.notEqual(req1.agent, req2.agent) 140 | t.end() 141 | }) 142 | 143 | tape('cleanup', function(t) { 144 | s.close(function() { 145 | t.end() 146 | }) 147 | }) 148 | -------------------------------------------------------------------------------- /tests/test-body.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var server = require('./server') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var s = server.createServer() 8 | 9 | tape('setup', function(t) { 10 | s.listen(s.port, function() { 11 | t.end() 12 | }) 13 | }) 14 | 15 | function addTest(name, data) { 16 | tape('test ' + name, function(t) { 17 | s.on('/' + name, data.resp) 18 | data.uri = s.url + '/' + name 19 | request(data, function (err, resp, body) { 20 | t.equal(err, null) 21 | if (data.expectBody && Buffer.isBuffer(data.expectBody)) { 22 | t.deepEqual(data.expectBody.toString(), body.toString()) 23 | } else if (data.expectBody) { 24 | t.deepEqual(data.expectBody, body) 25 | } 26 | t.end() 27 | }) 28 | }) 29 | } 30 | 31 | addTest('testGet', { 32 | resp : server.createGetResponse('TESTING!') 33 | , expectBody: 'TESTING!' 34 | }) 35 | 36 | addTest('testGetChunkBreak', { 37 | resp : server.createChunkResponse( 38 | [ new Buffer([239]) 39 | , new Buffer([163]) 40 | , new Buffer([191]) 41 | , new Buffer([206]) 42 | , new Buffer([169]) 43 | , new Buffer([226]) 44 | , new Buffer([152]) 45 | , new Buffer([131]) 46 | ]) 47 | , expectBody: '\uF8FF\u03A9\u2603' 48 | }) 49 | 50 | addTest('testGetBuffer', { 51 | resp : server.createGetResponse(new Buffer('TESTING!')) 52 | , encoding: null 53 | , expectBody: new Buffer('TESTING!') 54 | }) 55 | 56 | addTest('testGetEncoding', { 57 | resp : server.createGetResponse(new Buffer('efa3bfcea9e29883', 'hex')) 58 | , encoding: 'hex' 59 | , expectBody: 'efa3bfcea9e29883' 60 | }) 61 | 62 | addTest('testGetUTF', { 63 | resp: server.createGetResponse(new Buffer([0xEF, 0xBB, 0xBF, 226, 152, 131])) 64 | , encoding: 'utf8' 65 | , expectBody: '\u2603' 66 | }) 67 | 68 | addTest('testGetJSON', { 69 | resp : server.createGetResponse('{"test":true}', 'application/json') 70 | , json : true 71 | , expectBody: {'test':true} 72 | }) 73 | 74 | addTest('testPutString', { 75 | resp : server.createPostValidator('PUTTINGDATA') 76 | , method : 'PUT' 77 | , body : 'PUTTINGDATA' 78 | }) 79 | 80 | addTest('testPutBuffer', { 81 | resp : server.createPostValidator('PUTTINGDATA') 82 | , method : 'PUT' 83 | , body : new Buffer('PUTTINGDATA') 84 | }) 85 | 86 | addTest('testPutJSON', { 87 | resp : server.createPostValidator(JSON.stringify({foo: 'bar'})) 88 | , method: 'PUT' 89 | , json: {foo: 'bar'} 90 | }) 91 | 92 | addTest('testPutMultipart', { 93 | resp: server.createPostValidator( 94 | '--__BOUNDARY__\r\n' + 95 | 'content-type: text/html\r\n' + 96 | '\r\n' + 97 | 'Oh hi.' + 98 | '\r\n--__BOUNDARY__\r\n\r\n' + 99 | 'Oh hi.' + 100 | '\r\n--__BOUNDARY__--' 101 | ) 102 | , method: 'PUT' 103 | , multipart: 104 | [ {'content-type': 'text/html', 'body': 'Oh hi.'} 105 | , {'body': 'Oh hi.'} 106 | ] 107 | }) 108 | 109 | addTest('testPutMultipartPreambleCRLF', { 110 | resp: server.createPostValidator( 111 | '\r\n--__BOUNDARY__\r\n' + 112 | 'content-type: text/html\r\n' + 113 | '\r\n' + 114 | 'Oh hi.' + 115 | '\r\n--__BOUNDARY__\r\n\r\n' + 116 | 'Oh hi.' + 117 | '\r\n--__BOUNDARY__--' 118 | ) 119 | , method: 'PUT' 120 | , preambleCRLF: true 121 | , multipart: 122 | [ {'content-type': 'text/html', 'body': 'Oh hi.'} 123 | , {'body': 'Oh hi.'} 124 | ] 125 | }) 126 | 127 | addTest('testPutMultipartPostambleCRLF', { 128 | resp: server.createPostValidator( 129 | '\r\n--__BOUNDARY__\r\n' + 130 | 'content-type: text/html\r\n' + 131 | '\r\n' + 132 | 'Oh hi.' + 133 | '\r\n--__BOUNDARY__\r\n\r\n' + 134 | 'Oh hi.' + 135 | '\r\n--__BOUNDARY__--' + 136 | '\r\n' 137 | ) 138 | , method: 'PUT' 139 | , preambleCRLF: true 140 | , postambleCRLF: true 141 | , multipart: 142 | [ {'content-type': 'text/html', 'body': 'Oh hi.'} 143 | , {'body': 'Oh hi.'} 144 | ] 145 | }) 146 | 147 | tape('cleanup', function(t) { 148 | s.close(function() { 149 | t.end() 150 | }) 151 | }) 152 | -------------------------------------------------------------------------------- /tests/test-baseUrl.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | , url = require('url') 7 | 8 | var s = http.createServer(function(req, res) { 9 | if (req.url === '/redirect/') { 10 | res.writeHead(302, { 11 | location : '/' 12 | }) 13 | } else { 14 | res.statusCode = 200 15 | res.setHeader('X-PATH', req.url) 16 | } 17 | res.end('ok') 18 | }) 19 | 20 | tape('setup', function(t) { 21 | s.listen(6767, function() { 22 | t.end() 23 | }) 24 | }) 25 | 26 | tape('baseUrl', function(t) { 27 | request('resource', { 28 | baseUrl: 'http://localhost:6767' 29 | }, function(err, resp, body) { 30 | t.equal(err, null) 31 | t.equal(body, 'ok') 32 | t.end() 33 | }) 34 | }) 35 | 36 | tape('baseUrl defaults', function(t) { 37 | var withDefaults = request.defaults({ 38 | baseUrl: 'http://localhost:6767' 39 | }) 40 | withDefaults('resource', function(err, resp, body) { 41 | t.equal(err, null) 42 | t.equal(body, 'ok') 43 | t.end() 44 | }) 45 | }) 46 | 47 | tape('baseUrl and redirects', function(t) { 48 | request('/', { 49 | baseUrl: 'http://localhost:6767/redirect' 50 | }, function(err, resp, body) { 51 | t.equal(err, null) 52 | t.equal(body, 'ok') 53 | t.equal(resp.headers['x-path'], '/') 54 | t.end() 55 | }) 56 | }) 57 | 58 | function addTest(baseUrl, uri, expected) { 59 | tape('test baseurl="' + baseUrl + '" uri="' + uri + '"', function(t) { 60 | request(uri, { baseUrl: baseUrl }, function(err, resp, body) { 61 | t.equal(err, null) 62 | t.equal(body, 'ok') 63 | t.equal(resp.headers['x-path'], expected) 64 | t.end() 65 | }) 66 | }) 67 | } 68 | 69 | addTest('http://localhost:6767', '', '/') 70 | addTest('http://localhost:6767/', '', '/') 71 | addTest('http://localhost:6767', '/', '/') 72 | addTest('http://localhost:6767/', '/', '/') 73 | addTest('http://localhost:6767/api', '', '/api') 74 | addTest('http://localhost:6767/api/', '', '/api/') 75 | addTest('http://localhost:6767/api', '/', '/api/') 76 | addTest('http://localhost:6767/api/', '/', '/api/') 77 | addTest('http://localhost:6767/api', 'resource', '/api/resource') 78 | addTest('http://localhost:6767/api/', 'resource', '/api/resource') 79 | addTest('http://localhost:6767/api', '/resource', '/api/resource') 80 | addTest('http://localhost:6767/api/', '/resource', '/api/resource') 81 | addTest('http://localhost:6767/api', 'resource/', '/api/resource/') 82 | addTest('http://localhost:6767/api/', 'resource/', '/api/resource/') 83 | addTest('http://localhost:6767/api', '/resource/', '/api/resource/') 84 | addTest('http://localhost:6767/api/', '/resource/', '/api/resource/') 85 | 86 | tape('error when baseUrl is not a String', function(t) { 87 | request('resource', { 88 | baseUrl: url.parse('http://localhost:6767/path') 89 | }, function(err, resp, body) { 90 | t.notEqual(err, null) 91 | t.equal(err.message, 'options.baseUrl must be a string') 92 | t.end() 93 | }) 94 | }) 95 | 96 | tape('error when uri is not a String', function(t) { 97 | request(url.parse('resource'), { 98 | baseUrl: 'http://localhost:6767/path' 99 | }, function(err, resp, body) { 100 | t.notEqual(err, null) 101 | t.equal(err.message, 'options.uri must be a string when using options.baseUrl') 102 | t.end() 103 | }) 104 | }) 105 | 106 | tape('error on baseUrl and uri with scheme', function(t) { 107 | request('http://localhost:6767/path/ignoring/baseUrl', { 108 | baseUrl: 'http://localhost:6767/path/' 109 | }, function(err, resp, body) { 110 | t.notEqual(err, null) 111 | t.equal(err.message, 'options.uri must be a path when using options.baseUrl') 112 | t.end() 113 | }) 114 | }) 115 | 116 | tape('error on baseUrl and uri with scheme-relative url', function(t) { 117 | request('//localhost:6767/path/ignoring/baseUrl', { 118 | baseUrl: 'http://localhost:6767/path/' 119 | }, function(err, resp, body) { 120 | t.notEqual(err, null) 121 | t.equal(err.message, 'options.uri must be a path when using options.baseUrl') 122 | t.end() 123 | }) 124 | }) 125 | 126 | tape('cleanup', function(t) { 127 | s.close(function() { 128 | t.end() 129 | }) 130 | }) 131 | -------------------------------------------------------------------------------- /tests/test-qs.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , tape = require('tape') 5 | 6 | // Run a querystring test. `options` can have the following keys: 7 | // - suffix : a string to be added to the URL 8 | // - qs : an object to be passed to request's `qs` option 9 | // - qsParseOptions : an object to be passed to request's `qsParseOptions` option 10 | // - qsStringifyOptions : an object to be passed to request's `qsStringifyOptions` option 11 | // - afterRequest : a function to execute after creating the request 12 | // - expected : the expected path of the request 13 | // - expectedQuerystring : expected path when using the querystring library 14 | function runTest(name, options) { 15 | var uri = 'http://www.google.com' + (options.suffix || '') 16 | var opts = { 17 | uri : uri, 18 | qsParseOptions: options.qsParseOptions, 19 | qsStringifyOptions: options.qsStringifyOptions 20 | } 21 | 22 | if (options.qs) { 23 | opts.qs = options.qs 24 | } 25 | 26 | tape(name + ' - using qs', function(t) { 27 | var r = request.get(opts) 28 | if (typeof options.afterRequest === 'function') { 29 | options.afterRequest(r) 30 | } 31 | process.nextTick(function() { 32 | t.equal(r.path, options.expected) 33 | r.abort() 34 | t.end() 35 | }) 36 | }) 37 | 38 | tape(name + ' - using querystring', function(t) { 39 | opts.useQuerystring = true 40 | var r = request.get(opts) 41 | if (typeof options.afterRequest === 'function') { 42 | options.afterRequest(r) 43 | } 44 | process.nextTick(function() { 45 | t.equal(r.path, options.expectedQuerystring || options.expected) 46 | r.abort() 47 | t.end() 48 | }) 49 | }) 50 | } 51 | 52 | function esc(str) { 53 | return str 54 | .replace(/\[/g, '%5B') 55 | .replace(/\]/g, '%5D') 56 | } 57 | 58 | runTest('adding a querystring', { 59 | qs : { q : 'search' }, 60 | expected : '/?q=search' 61 | }) 62 | 63 | runTest('replacing a querystring value', { 64 | suffix : '?q=abc', 65 | qs : { q : 'search' }, 66 | expected : '/?q=search' 67 | }) 68 | 69 | runTest('appending a querystring value to the ones present in the uri', { 70 | suffix : '?x=y', 71 | qs : { q : 'search' }, 72 | expected : '/?x=y&q=search' 73 | }) 74 | 75 | runTest('leaving a querystring alone', { 76 | suffix : '?x=y', 77 | expected : '/?x=y' 78 | }) 79 | 80 | runTest('giving empty qs property', { 81 | qs : {}, 82 | expected : '/' 83 | }) 84 | 85 | runTest('modifying the qs after creating the request', { 86 | qs : {}, 87 | afterRequest : function(r) { 88 | r.qs({ q : 'test' }) 89 | }, 90 | expected : '/?q=test' 91 | }) 92 | 93 | runTest('a query with an object for a value', { 94 | qs : { where : { foo: 'bar' } }, 95 | expected : esc('/?where[foo]=bar'), 96 | expectedQuerystring : '/?where=' 97 | }) 98 | 99 | runTest('a query with an array for a value', { 100 | qs : { order : ['bar', 'desc'] }, 101 | expected : esc('/?order[0]=bar&order[1]=desc'), 102 | expectedQuerystring : '/?order=bar&order=desc' 103 | }) 104 | 105 | runTest('pass options to the qs module via the qsParseOptions key', { 106 | suffix : '?a=1;b=2', 107 | qs: {}, 108 | qsParseOptions: { delimiter : ';' }, 109 | qsStringifyOptions: { delimiter : ';' }, 110 | expected : esc('/?a=1;b=2'), 111 | expectedQuerystring : '/?a=1%3Bb%3D2' 112 | }) 113 | 114 | runTest('pass options to the qs module via the qsStringifyOptions key', { 115 | qs : { order : ['bar', 'desc'] }, 116 | qsStringifyOptions: { arrayFormat : 'brackets' }, 117 | expected : esc('/?order[]=bar&order[]=desc'), 118 | expectedQuerystring : '/?order=bar&order=desc' 119 | }) 120 | 121 | runTest('pass options to the querystring module via the qsParseOptions key', { 122 | suffix : '?a=1;b=2', 123 | qs: {}, 124 | qsParseOptions: { sep : ';' }, 125 | qsStringifyOptions: { sep : ';' }, 126 | expected : esc('/?a=1%3Bb%3D2'), 127 | expectedQuerystring : '/?a=1;b=2' 128 | }) 129 | 130 | runTest('pass options to the querystring module via the qsStringifyOptions key', { 131 | qs : { order : ['bar', 'desc'] }, 132 | qsStringifyOptions: { sep : ';' }, 133 | expected : esc('/?order[0]=bar&order[1]=desc'), 134 | expectedQuerystring : '/?order=bar;order=desc' 135 | }) 136 | -------------------------------------------------------------------------------- /tests/server.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | , http = require('http') 5 | , path = require('path') 6 | , https = require('https') 7 | , stream = require('stream') 8 | , assert = require('assert') 9 | 10 | exports.port = 6767 11 | exports.portSSL = 16167 12 | 13 | exports.createServer = function (port) { 14 | port = port || exports.port 15 | var s = http.createServer(function (req, resp) { 16 | s.emit(req.url.replace(/(\?.*)/, ''), req, resp) 17 | }) 18 | s.port = port 19 | s.url = 'http://localhost:' + port 20 | s.protocol = 'http' 21 | return s 22 | } 23 | 24 | exports.createEchoServer = function (port) { 25 | port = port || exports.port 26 | var s = http.createServer(function (req, resp) { 27 | var b = '' 28 | req.on('data', function (chunk) {b += chunk}) 29 | req.on('end', function () { 30 | resp.writeHead(200, {'content-type':'application/json'}) 31 | resp.write(JSON.stringify({ 32 | url: req.url, 33 | method: req.method, 34 | headers: req.headers, 35 | body: b 36 | })) 37 | resp.end() 38 | }) 39 | }) 40 | s.port = port 41 | s.url = 'http://localhost:' + port 42 | s.protocol = 'http' 43 | return s 44 | } 45 | 46 | exports.createSSLServer = function(port, opts) { 47 | port = port || exports.portSSL 48 | 49 | var i 50 | , options = { 'key' : path.join(__dirname, 'ssl', 'test.key') 51 | , 'cert': path.join(__dirname, 'ssl', 'test.crt') 52 | } 53 | if (opts) { 54 | for (i in opts) options[i] = opts[i] 55 | } 56 | 57 | for (i in options) { 58 | if (i !== 'requestCert' && i !== 'rejectUnauthorized' && i !== 'ciphers') { 59 | options[i] = fs.readFileSync(options[i]) 60 | } 61 | } 62 | 63 | var s = https.createServer(options, function (req, resp) { 64 | s.emit(req.url, req, resp) 65 | }) 66 | s.port = port 67 | s.url = 'https://localhost:' + port 68 | s.protocol = 'https' 69 | return s 70 | } 71 | 72 | exports.createPostStream = function (text) { 73 | var postStream = new stream.Stream() 74 | postStream.writeable = true 75 | postStream.readable = true 76 | setTimeout(function() { 77 | postStream.emit('data', new Buffer(text)) 78 | postStream.emit('end') 79 | }, 0) 80 | return postStream 81 | } 82 | exports.createPostValidator = function (text, reqContentType) { 83 | var l = function (req, resp) { 84 | var r = '' 85 | req.on('data', function (chunk) {r += chunk}) 86 | req.on('end', function () { 87 | if (req.headers['content-type'] && req.headers['content-type'].indexOf('boundary=') >= 0) { 88 | var boundary = req.headers['content-type'].split('boundary=')[1] 89 | text = text.replace(/__BOUNDARY__/g, boundary) 90 | } 91 | assert.equal(r, text) 92 | if (reqContentType) { 93 | assert.ok(req.headers['content-type']) 94 | assert.ok(~req.headers['content-type'].indexOf(reqContentType)) 95 | } 96 | resp.writeHead(200, {'content-type':'text/plain'}) 97 | resp.write(r) 98 | resp.end() 99 | }) 100 | } 101 | return l 102 | } 103 | exports.createPostJSONValidator = function (value, reqContentType) { 104 | var l = function (req, resp) { 105 | var r = '' 106 | req.on('data', function (chunk) {r += chunk}) 107 | req.on('end', function () { 108 | var parsedValue = JSON.parse(r) 109 | assert.deepEqual(parsedValue, value) 110 | if (reqContentType) { 111 | assert.ok(req.headers['content-type']) 112 | assert.ok(~req.headers['content-type'].indexOf(reqContentType)) 113 | } 114 | resp.writeHead(200, {'content-type':'application/json'}) 115 | resp.write(r) 116 | resp.end() 117 | }) 118 | } 119 | return l 120 | } 121 | exports.createGetResponse = function (text, contentType) { 122 | var l = function (req, resp) { 123 | contentType = contentType || 'text/plain' 124 | resp.writeHead(200, {'content-type':contentType}) 125 | resp.write(text) 126 | resp.end() 127 | } 128 | return l 129 | } 130 | exports.createChunkResponse = function (chunks, contentType) { 131 | var l = function (req, resp) { 132 | contentType = contentType || 'text/plain' 133 | resp.writeHead(200, {'content-type':contentType}) 134 | chunks.forEach(function (chunk) { 135 | resp.write(chunk) 136 | }) 137 | resp.end() 138 | } 139 | return l 140 | } 141 | -------------------------------------------------------------------------------- /tests/test-multipart-encoding.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , path = require('path') 5 | , request = require('../index') 6 | , fs = require('fs') 7 | , tape = require('tape') 8 | 9 | 10 | var localFile = path.join(__dirname, 'unicycle.jpg') 11 | var cases = { 12 | // based on body type 13 | '+array -stream': { 14 | options: { 15 | multipart: [{name: 'field', body: 'value'}] 16 | }, 17 | expected: {chunked: false} 18 | }, 19 | '+array +stream': { 20 | options: { 21 | multipart: [{name: 'file', body: null}] 22 | }, 23 | expected: {chunked: true} 24 | }, 25 | // encoding overrides body value 26 | '+array +encoding': { 27 | options: { 28 | headers: {'transfer-encoding': 'chunked'}, 29 | multipart: [{name: 'field', body: 'value'}] 30 | }, 31 | expected: {chunked: true} 32 | }, 33 | 34 | // based on body type 35 | '+object -stream': { 36 | options: { 37 | multipart: {data: [{name: 'field', body: 'value'}]} 38 | }, 39 | expected: {chunked: false} 40 | }, 41 | '+object +stream': { 42 | options: { 43 | multipart: {data: [{name: 'file', body: null}]} 44 | }, 45 | expected: {chunked: true} 46 | }, 47 | // encoding overrides body value 48 | '+object +encoding': { 49 | options: { 50 | headers: {'transfer-encoding': 'chunked'}, 51 | multipart: {data: [{name: 'field', body: 'value'}]} 52 | }, 53 | expected: {chunked: true} 54 | }, 55 | 56 | // based on body type 57 | '+object -chunked -stream': { 58 | options: { 59 | multipart: {chunked: false, data: [{name: 'field', body: 'value'}]} 60 | }, 61 | expected: {chunked: false} 62 | }, 63 | '+object -chunked +stream': { 64 | options: { 65 | multipart: {chunked: false, data: [{name: 'file', body: null}]} 66 | }, 67 | expected: {chunked: true} 68 | }, 69 | // chunked overrides body value 70 | '+object +chunked -stream': { 71 | options: { 72 | multipart: {chunked: true, data: [{name: 'field', body: 'value'}]} 73 | }, 74 | expected: {chunked: true} 75 | }, 76 | // encoding overrides chunked 77 | '+object +encoding -chunked': { 78 | options: { 79 | headers: {'transfer-encoding': 'chunked'}, 80 | multipart: {chunked: false, data: [{name: 'field', body: 'value'}]} 81 | }, 82 | expected: {chunked: true} 83 | } 84 | } 85 | 86 | function runTest(t, test) { 87 | 88 | var server = http.createServer(function(req, res) { 89 | 90 | t.ok(req.headers['content-type'].match(/^multipart\/related; boundary=[^\s;]+$/)) 91 | 92 | if (test.expected.chunked) { 93 | t.ok(req.headers['transfer-encoding'] === 'chunked') 94 | t.notOk(req.headers['content-length']) 95 | } else { 96 | t.ok(req.headers['content-length']) 97 | t.notOk(req.headers['transfer-encoding']) 98 | } 99 | 100 | // temp workaround 101 | var data = '' 102 | req.setEncoding('utf8') 103 | 104 | req.on('data', function(d) { 105 | data += d 106 | }) 107 | 108 | req.on('end', function() { 109 | // check for the fields traces 110 | if (test.expected.chunked && data.indexOf('name: file') !== -1) { 111 | // file 112 | t.ok(data.indexOf('name: file') !== -1) 113 | // check for unicycle.jpg traces 114 | t.ok(data.indexOf('2005:06:21 01:44:12') !== -1) 115 | } else { 116 | // field 117 | t.ok(data.indexOf('name: field') !== -1) 118 | var parts = test.options.multipart.data || test.options.multipart 119 | t.ok(data.indexOf(parts[0].body) !== -1) 120 | } 121 | 122 | res.writeHead(200) 123 | res.end() 124 | }) 125 | }) 126 | 127 | server.listen(6767, function() { 128 | // @NOTE: multipartData properties must be set here 129 | // so that file read stream does not leak in node v0.8 130 | var parts = test.options.multipart.data || test.options.multipart 131 | if (parts[0].name === 'file') { 132 | parts[0].body = fs.createReadStream(localFile) 133 | } 134 | 135 | request.post('http://localhost:6767', test.options, function (err, res, body) { 136 | t.equal(err, null) 137 | t.equal(res.statusCode, 200) 138 | server.close(function () { 139 | t.end() 140 | }) 141 | }) 142 | }) 143 | } 144 | 145 | Object.keys(cases).forEach(function (name) { 146 | tape('multipart-encoding ' + name, function(t) { 147 | runTest(t, cases[name]) 148 | }) 149 | }) 150 | -------------------------------------------------------------------------------- /tests/test-har.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var path = require('path') 4 | var request = require('..') 5 | var tape = require('tape') 6 | var fixture = require('./fixtures/har.json') 7 | var server = require('./server') 8 | 9 | var s = server.createEchoServer() 10 | 11 | tape('setup', function (t) { 12 | s.listen(s.port, function () { 13 | t.end() 14 | }) 15 | }) 16 | 17 | tape('application-form-encoded', function (t) { 18 | var options = { 19 | url: s.url, 20 | har: fixture['application-form-encoded'] 21 | } 22 | 23 | request(options, function (err, res, body) { 24 | var json = JSON.parse(body) 25 | 26 | t.equal(err, null) 27 | t.equal(json.body, 'foo=bar&hello=world') 28 | t.end() 29 | }) 30 | }) 31 | 32 | tape('application-json', function (t) { 33 | var options = { 34 | url: s.url, 35 | har: fixture['application-json'] 36 | } 37 | 38 | request(options, function (err, res, body) { 39 | t.equal(err, null) 40 | t.equal(body.body, fixture['application-json'].postData.text) 41 | t.end() 42 | }) 43 | }) 44 | 45 | tape('cookies', function (t) { 46 | var options = { 47 | url: s.url, 48 | har: fixture.cookies 49 | } 50 | 51 | request(options, function (err, res, body) { 52 | var json = JSON.parse(body) 53 | 54 | t.equal(err, null) 55 | t.equal(json.headers.cookie, 'foo=bar; bar=baz') 56 | t.end() 57 | }) 58 | }) 59 | 60 | tape('custom-method', function (t) { 61 | var options = { 62 | url: s.url, 63 | har: fixture['custom-method'] 64 | } 65 | 66 | request(options, function (err, res, body) { 67 | var json = JSON.parse(body) 68 | 69 | t.equal(err, null) 70 | t.equal(json.method, fixture['custom-method'].method) 71 | t.end() 72 | }) 73 | }) 74 | 75 | tape('headers', function (t) { 76 | var options = { 77 | url: s.url, 78 | har: fixture.headers 79 | } 80 | 81 | request(options, function (err, res, body) { 82 | var json = JSON.parse(body) 83 | 84 | t.equal(err, null) 85 | t.equal(json.headers['x-foo'], 'Bar') 86 | t.end() 87 | }) 88 | }) 89 | 90 | tape('multipart-data', function (t) { 91 | var options = { 92 | url: s.url, 93 | har: fixture['multipart-data'] 94 | } 95 | 96 | request(options, function (err, res, body) { 97 | var json = JSON.parse(body) 98 | 99 | t.equal(err, null) 100 | t.ok(~json.headers['content-type'].indexOf('multipart/form-data')) 101 | t.ok(~json.body.indexOf('Content-Disposition: form-data; name="foo"; filename="hello.txt"\r\nContent-Type: text/plain\r\n\r\nHello World')) 102 | t.end() 103 | }) 104 | }) 105 | 106 | tape('multipart-file', function (t) { 107 | var options = { 108 | url: s.url, 109 | har: fixture['multipart-file'] 110 | } 111 | var absolutePath = path.resolve(__dirname, options.har.postData.params[0].fileName) 112 | options.har.postData.params[0].fileName = absolutePath 113 | 114 | request(options, function (err, res, body) { 115 | var json = JSON.parse(body) 116 | 117 | t.equal(err, null) 118 | t.ok(~json.headers['content-type'].indexOf('multipart/form-data')) 119 | t.ok(~json.body.indexOf('Content-Disposition: form-data; name="foo"; filename="unicycle.jpg"\r\nContent-Type: image/jpeg')) 120 | t.end() 121 | }) 122 | }) 123 | 124 | tape('multipart-form-data', function (t) { 125 | var options = { 126 | url: s.url, 127 | har: fixture['multipart-form-data'] 128 | } 129 | 130 | request(options, function (err, res, body) { 131 | var json = JSON.parse(body) 132 | 133 | t.equal(err, null) 134 | t.ok(~json.headers['content-type'].indexOf('multipart/form-data')) 135 | t.ok(~json.body.indexOf('Content-Disposition: form-data; name="foo"')) 136 | t.end() 137 | }) 138 | }) 139 | 140 | tape('query', function (t) { 141 | var options = { 142 | url: s.url + '/?fff=sss', 143 | har: fixture.query 144 | } 145 | 146 | request(options, function (err, res, body) { 147 | var json = JSON.parse(body) 148 | 149 | t.equal(err, null) 150 | t.equal(json.url, '/?fff=sss&foo%5B0%5D=bar&foo%5B1%5D=baz&baz=abc') 151 | t.end() 152 | }) 153 | }) 154 | 155 | tape('text/plain', function (t) { 156 | var options = { 157 | url: s.url, 158 | har: fixture['text-plain'] 159 | } 160 | 161 | request(options, function (err, res, body) { 162 | var json = JSON.parse(body) 163 | 164 | t.equal(err, null) 165 | t.equal(json.headers['content-type'], 'text/plain') 166 | t.equal(json.body, 'Hello World') 167 | t.end() 168 | }) 169 | }) 170 | 171 | tape('cleanup', function (t) { 172 | s.close(function () { 173 | t.end() 174 | }) 175 | }) 176 | -------------------------------------------------------------------------------- /tests/test-http-signature.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , httpSignature = require('http-signature') 6 | , tape = require('tape') 7 | 8 | var privateKeyPEMs = {} 9 | 10 | privateKeyPEMs['key-1'] = 11 | '-----BEGIN RSA PRIVATE KEY-----\n' + 12 | 'MIIEpAIBAAKCAQEAzWSJl+Z9Bqv00FVL5N3+JCUoqmQPjIlya1BbeqQroNQ5yG1i\n' + 13 | 'VbYTTnMRa1zQtR6r2fNvWeg94DvxivxIG9diDMnrzijAnYlTLOl84CK2vOxkj5b6\n' + 14 | '8zrLH9b/Gd6NOHsywo8IjvXvCeTfca5WUHcuVi2lT9VjygFs1ILG4RyeX1BXUumu\n' + 15 | 'Y8fzmposxLYdMxCqUTzAn0u9Saq2H2OVj5u114wS7OQPigu6G99dpn/iPHa3zBm8\n' + 16 | '7baBWDbqZWRW0BP3K6eqq8sut1+NLhNW8ADPTdnO/SO+kvXy7fqd8atSn+HlQcx6\n' + 17 | 'tW42dhXf3E9uE7K78eZtW0KvfyNGAjsI1Fft2QIDAQABAoIBAG1exe3/LEBrPLfb\n' + 18 | 'U8iRdY0lxFvHYIhDgIwohC3wUdMYb5SMurpNdEZn+7Sh/fkUVgp/GKJViu1mvh52\n' + 19 | 'bKd2r52DwG9NQBQjVgkqY/auRYSglIPpr8PpYNSZlcneunCDGeqEY9hMmXc5Ssqs\n' + 20 | 'PQYoEKKPN+IlDTg6PguDgAfLR4IUvt9KXVvmB/SSgV9tSeTy35LECt1Lq3ozbUgu\n' + 21 | '30HZI3U6/7H+X22Pxxf8vzBtzkg5rRCLgv+OeNPo16xMnqbutt4TeqEkxRv5rtOo\n' + 22 | '/A1i9khBeki0OJAFJsE82qnaSZodaRsxic59VnN8sWBwEKAt87tEu5A3K3j4XSDU\n' + 23 | '/avZxAECgYEA+pS3DvpiQLtHlaO3nAH6MxHRrREOARXWRDe5nUQuUNpS1xq9wte6\n' + 24 | 'DkFtba0UCvDLic08xvReTCbo9kH0y6zEy3zMpZuJlKbcWCkZf4S5miYPI0RTZtF8\n' + 25 | 'yps6hWqzYFSiO9hMYws9k4OJLxX0x3sLK7iNZ32ujcSrkPBSiBr0gxkCgYEA0dWl\n' + 26 | '637K41AJ/zy0FP0syq+r4eIkfqv+/t6y2aQVUBvxJYrj9ci6XHBqoxpDV8lufVYj\n' + 27 | 'fUAfeI9/MZaWvQJRbnYLre0I6PJfLuCBIL5eflO77BGso165AF7QJZ+fwtgKv3zv\n' + 28 | 'ZX75eudCSS/cFo0po9hlbcLMT4B82zEkgT8E2MECgYEAnz+3/wrdOmpLGiyL2dff\n' + 29 | '3GjsqmJ2VfY8z+niSrI0BSpbD11tT9Ct67VlCBjA7hsOH6uRfpd6/kaUMzzDiFVq\n' + 30 | 'VDAiFvV8QD6zNkwYalQ9aFvbrvwTTPrBpjl0vamMCiJ/YC0cjq1sGr2zh3sar1Ph\n' + 31 | 'S43kP+s97dcZeelhaiJHVrECgYEAsx61q/loJ/LDFeYzs1cLTVn4V7I7hQY9fkOM\n' + 32 | 'WM0AhInVqD6PqdfXfeFYpjJdGisQ7l0BnoGGW9vir+nkcyPvb2PFRIr6+B8tsU5j\n' + 33 | '7BeVgjDoUfQkcrEBK5fEBtnj/ud9BUkY8oMZZBjVNLRuI7IMwZiPvMp0rcj4zAN/\n' + 34 | 'LfUlpgECgYArBvFcBxSkNAzR3Rtteud1YDboSKluRM37Ey5plrn4BS0DD0jm++aD\n' + 35 | '0pG2Hsik000hibw92lCkzvvBVAqF8BuAcnPlAeYfsOaa97PGEjSKEN5bJVWZ9/om\n' + 36 | '9FV1axotRN2XWlwrhixZLEaagkREXhgQc540FS5O8IaI2Vpa80Atzg==\n' + 37 | '-----END RSA PRIVATE KEY-----' 38 | 39 | var publicKeyPEMs = {} 40 | 41 | publicKeyPEMs['key-1'] = 42 | '-----BEGIN PUBLIC KEY-----\n' + 43 | 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzWSJl+Z9Bqv00FVL5N3+\n' + 44 | 'JCUoqmQPjIlya1BbeqQroNQ5yG1iVbYTTnMRa1zQtR6r2fNvWeg94DvxivxIG9di\n' + 45 | 'DMnrzijAnYlTLOl84CK2vOxkj5b68zrLH9b/Gd6NOHsywo8IjvXvCeTfca5WUHcu\n' + 46 | 'Vi2lT9VjygFs1ILG4RyeX1BXUumuY8fzmposxLYdMxCqUTzAn0u9Saq2H2OVj5u1\n' + 47 | '14wS7OQPigu6G99dpn/iPHa3zBm87baBWDbqZWRW0BP3K6eqq8sut1+NLhNW8ADP\n' + 48 | 'TdnO/SO+kvXy7fqd8atSn+HlQcx6tW42dhXf3E9uE7K78eZtW0KvfyNGAjsI1Fft\n' + 49 | '2QIDAQAB\n' + 50 | '-----END PUBLIC KEY-----' 51 | 52 | publicKeyPEMs['key-2'] = 53 | '-----BEGIN PUBLIC KEY-----\n' + 54 | 'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqp04VVr9OThli9b35Omz\n' + 55 | 'VqSfWbsoQuRrgyWsrNRn3XkFmbWw4FzZwQ42OgGMzQ84Ta4d9zGKKQyFriTiPjPf\n' + 56 | 'xhhrsaJnDuybcpVhcr7UNKjSZ0S59tU3hpRiEz6hO+Nc/OSSLkvalG0VKrxOln7J\n' + 57 | 'LK/h3rNS/l6wDZ5S/KqsI6CYtV2ZLpn3ahLrizvEYNY038Qcm38qMWx+VJAvZ4di\n' + 58 | 'qqmW7RLIsLT59SWmpXdhFKnkYYGhxrk1Mwl22dBTJNY5SbriU5G3gWgzYkm8pgHr\n' + 59 | '6CtrXch9ciJAcDJehPrKXNvNDOdUh8EW3fekNJerF1lWcwQg44/12v8sDPyfbaKB\n' + 60 | 'dQIDAQAB\n' + 61 | '-----END PUBLIC KEY-----' 62 | 63 | var server = http.createServer(function (req, res) { 64 | var parsed = httpSignature.parseRequest(req) 65 | var publicKeyPEM = publicKeyPEMs[parsed.keyId] 66 | var verified = httpSignature.verifySignature(parsed, publicKeyPEM) 67 | res.writeHead(verified ? 200 : 400) 68 | res.end() 69 | }) 70 | 71 | tape('setup', function(t) { 72 | server.listen(6767, function() { 73 | t.end() 74 | }) 75 | }) 76 | 77 | tape('correct key', function(t) { 78 | var options = { 79 | httpSignature: { 80 | keyId: 'key-1', 81 | key: privateKeyPEMs['key-1'] 82 | } 83 | } 84 | request('http://localhost:6767', options, function(err, res, body) { 85 | t.equal(err, null) 86 | t.equal(200, res.statusCode) 87 | t.end() 88 | }) 89 | }) 90 | 91 | tape('incorrect key', function(t) { 92 | var options = { 93 | httpSignature: { 94 | keyId: 'key-2', 95 | key: privateKeyPEMs['key-1'] 96 | } 97 | } 98 | request('http://localhost:6767', options, function(err, res, body) { 99 | t.equal(err, null) 100 | t.equal(400, res.statusCode) 101 | t.end() 102 | }) 103 | }) 104 | 105 | tape('cleanup', function(t) { 106 | server.close(function() { 107 | t.end() 108 | }) 109 | }) 110 | -------------------------------------------------------------------------------- /tests/test-headers.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var server = require('./server') 4 | , request = require('../index') 5 | , util = require('util') 6 | , tape = require('tape') 7 | 8 | var s = server.createServer() 9 | 10 | s.on('/redirect/from', function(req, res) { 11 | res.writeHead(301, { 12 | location : '/redirect/to' 13 | }) 14 | res.end() 15 | }) 16 | 17 | s.on('/redirect/to', function(req, res) { 18 | res.end('ok') 19 | }) 20 | 21 | s.on('/headers.json', function(req, res) { 22 | res.writeHead(200, { 23 | 'Content-Type': 'application/json' 24 | }) 25 | 26 | res.end(JSON.stringify(req.headers)) 27 | }) 28 | 29 | tape('setup', function(t) { 30 | s.listen(s.port, function() { 31 | t.end() 32 | }) 33 | }) 34 | 35 | function runTest(name, path, requestObj, serverAssertFn) { 36 | tape(name, function(t) { 37 | s.on('/' + path, function(req, res) { 38 | serverAssertFn(t, req, res) 39 | res.writeHead(200) 40 | res.end() 41 | }) 42 | requestObj.url = s.url + '/' + path 43 | request(requestObj, function(err, res, body) { 44 | t.equal(err, null) 45 | t.equal(res.statusCode, 200) 46 | t.end() 47 | }) 48 | }) 49 | } 50 | 51 | runTest( 52 | '#125: headers.cookie with no cookie jar', 53 | 'no-jar', 54 | {headers: {cookie: 'foo=bar'}}, 55 | function(t, req, res) { 56 | t.equal(req.headers.cookie, 'foo=bar') 57 | }) 58 | 59 | var jar = request.jar() 60 | jar.setCookie('quux=baz', s.url) 61 | runTest( 62 | '#125: headers.cookie + cookie jar', 63 | 'header-and-jar', 64 | {jar: jar, headers: {cookie: 'foo=bar'}}, 65 | function(t, req, res) { 66 | t.equal(req.headers.cookie, 'foo=bar; quux=baz') 67 | }) 68 | 69 | var jar2 = request.jar() 70 | jar2.setCookie('quux=baz; Domain=foo.bar.com', s.url, {ignoreError: true}) 71 | runTest( 72 | '#794: ignore cookie parsing and domain errors', 73 | 'ignore-errors', 74 | {jar: jar2, headers: {cookie: 'foo=bar'}}, 75 | function(t, req, res) { 76 | t.equal(req.headers.cookie, 'foo=bar') 77 | }) 78 | 79 | runTest( 80 | '#784: override content-type when json is used', 81 | 'json', 82 | { 83 | json: true, 84 | method: 'POST', 85 | headers: { 'content-type': 'application/json; charset=UTF-8' }, 86 | body: { hello: 'my friend' }}, 87 | function(t, req, res) { 88 | t.equal(req.headers['content-type'], 'application/json; charset=UTF-8') 89 | } 90 | ) 91 | 92 | runTest( 93 | 'neither headers.cookie nor a cookie jar is specified', 94 | 'no-cookie', 95 | {}, 96 | function(t, req, res) { 97 | t.equal(req.headers.cookie, undefined) 98 | }) 99 | 100 | tape('upper-case Host header and redirect', function(t) { 101 | // Horrible hack to observe the raw data coming to the server (before Node 102 | // core lower-cases the headers) 103 | var rawData = '' 104 | 105 | s.on('connection', function(socket) { 106 | if (socket.ondata) { 107 | var ondata = socket.ondata 108 | } 109 | function handledata (d, start, end) { 110 | if (ondata) { 111 | rawData += d.slice(start, end).toString() 112 | return ondata.apply(this, arguments) 113 | } else { 114 | rawData += d 115 | } 116 | } 117 | socket.on('data', handledata) 118 | socket.ondata = handledata 119 | }) 120 | 121 | function checkHostHeader(host) { 122 | t.ok( 123 | new RegExp('^Host: ' + host + '$', 'm').test(rawData), 124 | util.format( 125 | 'Expected "Host: %s" in data "%s"', 126 | host, rawData.trim().replace(/\r?\n/g, '\\n'))) 127 | rawData = '' 128 | } 129 | 130 | var redirects = 0 131 | request({ 132 | url : s.url + '/redirect/from', 133 | headers : { Host : '127.0.0.1' } 134 | }, function(err, res, body) { 135 | t.equal(err, null) 136 | t.equal(res.statusCode, 200) 137 | t.equal(body, 'ok') 138 | t.equal(redirects, 1) 139 | // XXX should the host header change like this after a redirect? 140 | checkHostHeader('localhost:' + s.port) 141 | t.end() 142 | }).on('redirect', function() { 143 | redirects++ 144 | t.equal(this.uri.href, s.url + '/redirect/to') 145 | checkHostHeader('127.0.0.1') 146 | }) 147 | }) 148 | 149 | tape('undefined headers', function(t) { 150 | request({ 151 | url: s.url + '/headers.json', 152 | headers: { 153 | 'X-TEST-1': 'test1', 154 | 'X-TEST-2': undefined 155 | }, 156 | json: true 157 | }, function(err, res, body) { 158 | t.equal(err, null) 159 | t.equal(body['x-test-1'], 'test1') 160 | t.equal(typeof body['x-test-2'], 'undefined') 161 | t.end() 162 | }) 163 | }) 164 | 165 | tape('cleanup', function(t) { 166 | s.close(function() { 167 | t.end() 168 | }) 169 | }) 170 | -------------------------------------------------------------------------------- /lib/options.js: -------------------------------------------------------------------------------- 1 | 2 | var Headers = require('@request/headers') 3 | var har = require('./har') 4 | var baseURL = require('./base-url') 5 | 6 | 7 | function qs (options) { 8 | if (options.useQuerystring) { 9 | options.parse.querystring = options.qsParseOptions || {} 10 | options.stringify.querystring = options.qsStringifyOptions || {} 11 | } 12 | else { 13 | if (options.qsParseOptions) { 14 | options.parse.qs = options.qsParseOptions 15 | } 16 | if (options.qsStringifyOptions) { 17 | options.stringify.qs = options.qsStringifyOptions 18 | } 19 | } 20 | delete options.useQuerystring 21 | delete options.qsParseOptions 22 | delete options.qsStringifyOptions 23 | } 24 | 25 | function redirect (options) { 26 | if (options.redirect === undefined && options.followRedirect !== false) { 27 | options.redirect = {max: 10} 28 | } 29 | if (options.followAllRedirects) { 30 | options.redirect.all = true 31 | } 32 | if (typeof options.followRedirect === 'function') { 33 | options.redirect.allow = options.followRedirect 34 | } 35 | if (options.removeRefererHeader) { 36 | options.redirect.removeReferer = options.removeRefererHeader 37 | } 38 | if (options.maxRedirects) { 39 | options.redirect.max = options.maxRedirects 40 | } 41 | } 42 | 43 | function json (options) { 44 | var regex = /^application\/x-www-form-urlencoded\b/ 45 | var type = options.headers.get('content-type') 46 | if (options.json) { 47 | if (options.parse.json === undefined) { 48 | options.parse.json = true 49 | } 50 | if (typeof options.json === 'boolean') { 51 | if (options.body !== undefined && !regex.test(type)) { 52 | options.json = options.body 53 | delete options.body 54 | } 55 | else { 56 | delete options.json 57 | } 58 | } 59 | } 60 | if (options.jsonReviver) { 61 | options.parse.json = options.jsonReviver 62 | } 63 | } 64 | 65 | function form (options) { 66 | var regex = /^application\/x-www-form-urlencoded\b/ 67 | var type = options.headers.get('content-type') 68 | if (options.body !== undefined && regex.test(type)) { 69 | options.form = options.body 70 | delete options.body 71 | } 72 | } 73 | 74 | function formData (options) { 75 | function generateBoundary () { 76 | // This generates a 50 character boundary similar to those used by Firefox 77 | // They are optimized for boyer-moore parsing 78 | var boundary = '--------------------------' 79 | for (var i = 0; i < 24; i++) { 80 | boundary += Math.floor(Math.random() * 10).toString(16) 81 | } 82 | 83 | return boundary 84 | } 85 | if (options.formData) { 86 | if (!options.headers.get('content-type')) { 87 | options.headers.set( 88 | 'content-type', 'multipart/form-data; boundary=' + generateBoundary()) 89 | } 90 | options.multipart = options.formData 91 | } 92 | } 93 | 94 | function proxy (options) { 95 | if (options.proxy) { 96 | if (options.proxyHeaderWhiteList || options.proxyHeaderExclusiveList) { 97 | options.proxy = { 98 | url: options.proxy, 99 | headers: { 100 | allow: options.proxyHeaderWhiteList, 101 | exclusive: options.proxyHeaderExclusiveList 102 | } 103 | } 104 | } 105 | } 106 | } 107 | 108 | function auth (options) { 109 | if (options.auth) { 110 | if (typeof options.auth.bearer === 'function') { 111 | options.auth.bearer = options.auth.bearer() 112 | } 113 | } 114 | } 115 | 116 | function agent (options) { 117 | if (options.agentOptions) { 118 | 119 | } 120 | if (options.agentClass) { 121 | options.agent = new options.agentClass(options.agentOptions) 122 | } 123 | } 124 | 125 | exports.prepare = function (options) { 126 | if (options.har) { 127 | har(options) 128 | } 129 | 130 | options.headers = new Headers(options.headers) 131 | 132 | if (options.baseUrl) { 133 | baseURL(null, options) 134 | } 135 | 136 | if (options.parse === undefined) { 137 | options.parse = {} 138 | } 139 | if (options.stringify === undefined) { 140 | options.stringify = {} 141 | } 142 | 143 | if (options.encoding === null) { 144 | options.encoding = 'binary' 145 | } 146 | 147 | if (options.jar) { 148 | options.cookie = options.jar 149 | } 150 | 151 | if (options.length === undefined) { 152 | options.length = true 153 | } 154 | 155 | if (options.end === undefined) { 156 | options.end = true 157 | } 158 | 159 | redirect(options) 160 | agent(options) 161 | qs(options) 162 | json(options) 163 | form(options) 164 | formData(options) 165 | proxy(options) 166 | auth(options) 167 | 168 | options.headers = options.headers.toObject() 169 | } 170 | -------------------------------------------------------------------------------- /tests/test-form-data.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , path = require('path') 5 | , mime = require('mime-types') 6 | , request = require('../index') 7 | , fs = require('fs') 8 | , tape = require('tape') 9 | 10 | function runTest(t, options) { 11 | var remoteFile = path.join(__dirname, 'googledoodle.jpg') 12 | , localFile = path.join(__dirname, 'unicycle.jpg') 13 | , multipartFormData = {} 14 | 15 | var server = http.createServer(function(req, res) { 16 | if (req.url === '/file') { 17 | res.writeHead(200, {'content-type': 'image/jpg', 'content-length':7187}) 18 | res.end(fs.readFileSync(remoteFile), 'binary') 19 | return 20 | } 21 | 22 | if (options.auth) { 23 | if (!req.headers.authorization) { 24 | res.writeHead(401, {'www-authenticate': 'Basic realm="Private"'}) 25 | res.end() 26 | return 27 | } else { 28 | t.ok(req.headers.authorization === 'Basic ' + new Buffer('user:pass').toString('base64')) 29 | } 30 | } 31 | 32 | t.ok(/multipart\/form-data; boundary=--------------------------\d+/ 33 | .test(req.headers['content-type'])) 34 | 35 | // temp workaround 36 | var data = '' 37 | req.setEncoding('utf8') 38 | 39 | req.on('data', function(d) { 40 | data += d 41 | }) 42 | 43 | req.on('end', function() { 44 | // check for the fields' traces 45 | 46 | // 1st field : my_field 47 | t.ok( data.indexOf('form-data; name="my_field"') !== -1 ) 48 | t.ok( data.indexOf(multipartFormData.my_field) !== -1 ) 49 | 50 | // 2nd field : my_buffer 51 | t.ok( data.indexOf('form-data; name="my_buffer"') !== -1 ) 52 | t.ok( data.indexOf(multipartFormData.my_buffer) !== -1 ) 53 | 54 | // 3rd field : my_file 55 | t.ok( data.indexOf('form-data; name="my_file"') !== -1 ) 56 | t.ok( data.indexOf('; filename="' + path.basename(multipartFormData.my_file.path) + '"') !== -1 ) 57 | // check for unicycle.jpg traces 58 | t.ok( data.indexOf('2005:06:21 01:44:12') !== -1 ) 59 | t.ok( data.indexOf('Content-Type: ' + mime.lookup(multipartFormData.my_file.path) ) !== -1 ) 60 | 61 | // 4th field : remote_file 62 | t.ok( data.indexOf('form-data; name="remote_file"') !== -1 ) 63 | t.ok( data.indexOf('; filename="' + path.basename(multipartFormData.remote_file.path) + '"') !== -1 ) 64 | 65 | // 5th field : file with metadata 66 | t.ok( data.indexOf('form-data; name="secret_file"') !== -1 ) 67 | t.ok( data.indexOf('Content-Disposition: form-data; name="secret_file"; filename="topsecret.jpg"') !== -1 ) 68 | t.ok( data.indexOf('Content-Type: image/custom') !== -1 ) 69 | 70 | // 6th field : batch of files 71 | t.ok( data.indexOf('form-data; name="batch"') !== -1 ) 72 | t.ok( data.match(/form-data; name="batch"/g).length === 2 ) 73 | 74 | // check for http://localhost:6767/file traces 75 | t.ok( data.indexOf('Photoshop ICC') !== -1 ) 76 | t.ok( data.indexOf('Content-Type: ' + mime.lookup(remoteFile) ) !== -1 ) 77 | 78 | res.writeHead(200) 79 | res.end(options.json ? JSON.stringify({status: 'done'}) : 'done') 80 | }) 81 | }) 82 | 83 | server.listen(6767, function() { 84 | 85 | // @NOTE: multipartFormData properties must be set here so that my_file read stream does not leak in node v0.8 86 | multipartFormData.my_field = 'my_value' 87 | multipartFormData.my_buffer = new Buffer([1, 2, 3]) 88 | multipartFormData.my_file = fs.createReadStream(localFile) 89 | multipartFormData.remote_file = request('http://localhost:6767/file') 90 | multipartFormData.secret_file = { 91 | value: fs.createReadStream(localFile), 92 | options: { 93 | filename: 'topsecret.jpg', 94 | contentType: 'image/custom' 95 | } 96 | } 97 | multipartFormData.batch = [ 98 | fs.createReadStream(localFile), 99 | fs.createReadStream(localFile) 100 | ] 101 | 102 | var reqOptions = { 103 | url: 'http://localhost:6767/upload', 104 | formData: multipartFormData 105 | } 106 | if (options.json) { 107 | reqOptions.json = true 108 | } 109 | if (options.auth) { 110 | reqOptions.auth = {user: 'user', pass: 'pass', sendImmediately: false} 111 | } 112 | request.post(reqOptions, function (err, res, body) { 113 | t.equal(err, null) 114 | t.equal(res.statusCode, 200) 115 | t.deepEqual(body, options.json ? {status: 'done'} : 'done') 116 | server.close(function() { 117 | t.end() 118 | }) 119 | }) 120 | 121 | }) 122 | } 123 | 124 | tape('multipart formData', function(t) { 125 | runTest(t, {json: false}) 126 | }) 127 | 128 | tape('multipart formData + JSON', function(t) { 129 | runTest(t, {json: true}) 130 | }) 131 | 132 | tape('multipart formData + basic auth', function(t) { 133 | runTest(t, {json: false, auth: true}) 134 | }) 135 | -------------------------------------------------------------------------------- /tests/test-bearer-auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var assert = require('assert') 4 | , http = require('http') 5 | , request = require('../index') 6 | , tape = require('tape') 7 | 8 | var numBearerRequests = 0 9 | , bearerServer 10 | , port = 6767 11 | 12 | tape('setup', function(t) { 13 | bearerServer = http.createServer(function (req, res) { 14 | numBearerRequests++ 15 | 16 | var ok 17 | 18 | if (req.headers.authorization) { 19 | if (req.headers.authorization === 'Bearer theToken') { 20 | ok = true 21 | } else { 22 | // Bad auth header, don't send back WWW-Authenticate header 23 | ok = false 24 | } 25 | } else { 26 | // No auth header, send back WWW-Authenticate header 27 | ok = false 28 | res.setHeader('www-authenticate', 'Bearer realm="Private"') 29 | } 30 | 31 | if (req.url === '/post/') { 32 | var expectedContent = 'data_key=data_value' 33 | req.on('data', function(data) { 34 | assert.equal(data, expectedContent) 35 | }) 36 | assert.equal(req.method, 'POST') 37 | assert.equal(req.headers['content-length'], '' + expectedContent.length) 38 | assert.equal(req.headers['content-type'], 'application/x-www-form-urlencoded') 39 | } 40 | 41 | if (ok) { 42 | res.end('ok') 43 | } else { 44 | res.statusCode = 401 45 | res.end('401') 46 | } 47 | }).listen(port, function() { 48 | t.end() 49 | }) 50 | }) 51 | 52 | tape('bearer auth', function(t) { 53 | request({ 54 | 'method': 'GET', 55 | 'uri': 'http://localhost:6767/test/', 56 | 'auth': { 57 | 'bearer': 'theToken', 58 | 'sendImmediately': false 59 | } 60 | }, function(error, res, body) { 61 | t.equal(res.statusCode, 200) 62 | t.equal(numBearerRequests, 2) 63 | t.end() 64 | }) 65 | }) 66 | 67 | tape('bearer auth with default sendImmediately', function(t) { 68 | // If we don't set sendImmediately = false, request will send bearer auth 69 | request({ 70 | 'method': 'GET', 71 | 'uri': 'http://localhost:6767/test2/', 72 | 'auth': { 73 | 'bearer': 'theToken' 74 | } 75 | }, function(error, res, body) { 76 | t.equal(res.statusCode, 200) 77 | t.equal(numBearerRequests, 3) 78 | t.end() 79 | }) 80 | }) 81 | 82 | tape('', function(t) { 83 | request({ 84 | 'method': 'POST', 85 | 'form': { 'data_key': 'data_value' }, 86 | 'uri': 'http://localhost:6767/post/', 87 | 'auth': { 88 | 'bearer': 'theToken', 89 | 'sendImmediately': false 90 | } 91 | }, function(error, res, body) { 92 | t.equal(res.statusCode, 200) 93 | t.equal(numBearerRequests, 5) 94 | t.end() 95 | }) 96 | }) 97 | 98 | tape('using .auth, sendImmediately = false', function(t) { 99 | request 100 | .get('http://localhost:6767/test/') 101 | .auth(null, null, false, 'theToken') 102 | .on('response', function (res) { 103 | t.equal(res.statusCode, 200) 104 | t.equal(numBearerRequests, 7) 105 | t.end() 106 | }) 107 | }) 108 | 109 | tape('using .auth, sendImmediately = true', function(t) { 110 | request 111 | .get('http://localhost:6767/test/') 112 | .auth(null, null, true, 'theToken') 113 | .on('response', function (res) { 114 | t.equal(res.statusCode, 200) 115 | t.equal(numBearerRequests, 8) 116 | t.end() 117 | }) 118 | }) 119 | 120 | tape('bearer is a function', function(t) { 121 | request({ 122 | 'method': 'GET', 123 | 'uri': 'http://localhost:6767/test/', 124 | 'auth': { 125 | 'bearer': function() { return 'theToken' }, 126 | 'sendImmediately': false 127 | } 128 | }, function(error, res, body) { 129 | t.equal(res.statusCode, 200) 130 | t.equal(numBearerRequests, 10) 131 | t.end() 132 | }) 133 | }) 134 | 135 | tape('bearer is a function, path = test2', function(t) { 136 | // If we don't set sendImmediately = false, request will send bearer auth 137 | request({ 138 | 'method': 'GET', 139 | 'uri': 'http://localhost:6767/test2/', 140 | 'auth': { 141 | 'bearer': function() { return 'theToken' } 142 | } 143 | }, function(error, res, body) { 144 | t.equal(res.statusCode, 200) 145 | t.equal(numBearerRequests, 11) 146 | t.end() 147 | }) 148 | }) 149 | 150 | tape('no auth method', function(t) { 151 | request({ 152 | 'method': 'GET', 153 | 'uri': 'http://localhost:6767/test2/', 154 | 'auth': { 155 | 'bearer': undefined 156 | } 157 | }, function(error, res, body) { 158 | t.equal(error.message, 'no auth mechanism defined') 159 | t.end() 160 | }) 161 | }) 162 | 163 | tape('null bearer', function(t) { 164 | request({ 165 | 'method': 'GET', 166 | 'uri': 'http://localhost:6767/test2/', 167 | 'auth': { 168 | 'bearer': null 169 | } 170 | }, function(error, res, body) { 171 | t.equal(res.statusCode, 401) 172 | t.equal(numBearerRequests, 13) 173 | t.end() 174 | }) 175 | }) 176 | 177 | tape('cleanup', function(t) { 178 | bearerServer.close(function() { 179 | t.end() 180 | }) 181 | }) 182 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 2 | process.env.CORE_LIB = true 3 | 4 | var url = require('url') 5 | var extend = require('extend') 6 | var core = require('@request/core') 7 | var Cookie = require('./lib/cookie') 8 | var defaults = require('./lib/defaults') 9 | var params = require('./lib/params') 10 | var opts = require('./lib/options') 11 | var getProxyFromURI = require('./lib/getProxyFromURI') 12 | 13 | 14 | function initRequest (_options) { 15 | var options = core._lib.config.init(_options) 16 | var req = new core.Request(options.protocol) 17 | 18 | if (!options.hasOwnProperty('proxy')) { 19 | options.proxy = getProxyFromURI(options.url) 20 | } 21 | // if (options.proxy) { 22 | // if (options.url.protocol === 'https:' && options.tunnel === undefined) { 23 | // options.tunnel = true 24 | // } 25 | // } 26 | 27 | // response 28 | function state (res) { 29 | req.response = res 30 | 31 | req.headers = options.headers.toObject() 32 | req.uri = options.url 33 | req.localAddress = options.localAddress 34 | req.agent = options.agent 35 | req.agentOptions = _options.agentOptions 36 | 37 | req.req = req._req 38 | res.request = req 39 | res.toJSON = function () { 40 | return { 41 | statusCode: res.statusCode, 42 | body: res.body, 43 | headers: res.headers, 44 | request: JSON.parse(JSON.stringify(req)) 45 | } 46 | } 47 | } 48 | req.on('socket', state.bind()) 49 | req.on('redirect', state.bind()) 50 | req.on('onresponse', state.bind()) 51 | // timing 52 | req.on('onresponse', function (res) { 53 | if (_options.time) { 54 | res.once('end', function () { 55 | res.elapsedTime = (new Date().getTime() - _options.startTime) 56 | }) 57 | } 58 | }) 59 | 60 | // request 61 | req.path = options.url.path 62 | // timing 63 | req.on('request', function () { 64 | if (_options.time && !_options.startTime) { 65 | _options.startTime = new Date().getTime() 66 | } 67 | }) 68 | 69 | // qs 70 | core._lib.options.qs.request = (function (qs) { 71 | return function (req, options) { 72 | qs(req, options) 73 | req.path = options.url.path 74 | } 75 | }(core._lib.options.qs.request)) 76 | 77 | // auth 78 | core._lib.options.auth.request = (function (auth) { 79 | return function (req, options) { 80 | auth(req, options) 81 | req.headers = options.headers.toObject() 82 | req.uri = options.url 83 | req.path = options.url.path 84 | req.body = options.body 85 | if (options.auth) { 86 | req._auth = options.auth 87 | } 88 | } 89 | }(core._lib.options.auth.request)) 90 | 91 | // callback 92 | core._lib.options.callback.request = (function (callback) { 93 | return function (req, options) { 94 | options.callback = options.callback.bind(req) 95 | callback(req, options) 96 | } 97 | }(core._lib.options.callback.request)) 98 | 99 | // init request options 100 | core._lib.request(req, options) 101 | 102 | req.setHeader = function (key, value) { 103 | options.headers.set(key, value) 104 | } 105 | 106 | // create options methods 107 | req.qs = function (_options) { 108 | extend(true, options, {qs: _options}) 109 | core._lib.options.qs.request(req, options) 110 | return req 111 | } 112 | req.form = function (_options) { 113 | extend(true, options, {form: _options}) 114 | core._lib.options.form.request(req, options) 115 | return req 116 | } 117 | req.auth = function (user, pass, sendImmediately, bearer) { 118 | extend(true, options, 119 | {auth: {user: user, pass: pass, sendImmediately: sendImmediately, bearer: bearer}}) 120 | core._lib.options.auth.request(req, options) 121 | return req 122 | } 123 | 124 | req.toJSON = function () { 125 | return { 126 | uri: req.uri, 127 | method: req.method, 128 | headers: req.headers 129 | } 130 | } 131 | 132 | return req 133 | } 134 | 135 | function client (uri, options, callback) { 136 | var options = client.init(uri, options, callback) 137 | 138 | function paramsHaveRequestBody(params) { 139 | return ( 140 | params.body || 141 | params.requestBodyStream || 142 | (params.json && typeof params.json !== 'boolean') || 143 | params.multipart 144 | ) 145 | } 146 | if (options.method === 'HEAD' && paramsHaveRequestBody(options)) { 147 | throw new Error('HTTP HEAD requests MUST NOT include a request body.') 148 | } 149 | 150 | opts.prepare(options) 151 | 152 | // overwrite @request/core's entry point 153 | var req = initRequest(options) 154 | return req 155 | } 156 | 157 | client.init = params.init 158 | client.initParams = client.init 159 | 160 | client.jar = function (store) { 161 | return Cookie.jar(store) 162 | } 163 | 164 | client.cookie = function (str) { 165 | return Cookie.parse(str) 166 | } 167 | 168 | function verbFunc (verb) { 169 | var method = verb === 'del' ? 'DELETE' : verb.toUpperCase() 170 | return function (uri, options, callback) { 171 | var options = client.init(uri, options, callback) 172 | options.method = method 173 | return client(options, options.callback) 174 | } 175 | } 176 | 177 | client.get = verbFunc('get') 178 | client.head = verbFunc('head') 179 | client.post = verbFunc('post') 180 | client.put = verbFunc('put') 181 | client.patch = verbFunc('patch') 182 | client.del = verbFunc('del') 183 | 184 | client.defaults = function (options, requester) { 185 | return defaults.call(this, options, requester) 186 | } 187 | 188 | module.exports = client 189 | -------------------------------------------------------------------------------- /lib/har.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs') 4 | var util = require('util') 5 | var qs = require('querystring') 6 | var validate = require('har-validator') 7 | 8 | 9 | function reducer (obj, pair) { 10 | // new property ? 11 | if (obj[pair.name] === undefined) { 12 | obj[pair.name] = pair.value 13 | return obj 14 | } 15 | 16 | // existing? convert to array 17 | var arr = [ 18 | obj[pair.name], 19 | pair.value 20 | ] 21 | 22 | obj[pair.name] = arr 23 | 24 | return obj 25 | } 26 | 27 | function prep (data) { 28 | // construct utility properties 29 | data.queryObj = {} 30 | data.headersObj = {} 31 | data.postData.jsonObj = false 32 | data.postData.paramsObj = false 33 | 34 | // construct query objects 35 | if (data.queryString && data.queryString.length) { 36 | data.queryObj = data.queryString.reduce(reducer, {}) 37 | } 38 | 39 | // construct headers objects 40 | if (data.headers && data.headers.length) { 41 | // loweCase header keys 42 | data.headersObj = data.headers.reduceRight(function (headers, header) { 43 | headers[header.name] = header.value 44 | return headers 45 | }, {}) 46 | } 47 | 48 | // construct Cookie header 49 | if (data.cookies && data.cookies.length) { 50 | var cookies = data.cookies.map(function (cookie) { 51 | return cookie.name + '=' + cookie.value 52 | }) 53 | 54 | if (cookies.length) { 55 | data.headersObj.cookie = cookies.join('; ') 56 | } 57 | } 58 | 59 | // prep body 60 | function some (arr) { 61 | return arr.some(function (type) { 62 | return data.postData.mimeType.indexOf(type) === 0 63 | }) 64 | } 65 | 66 | if (some([ 67 | 'multipart/mixed', 68 | 'multipart/related', 69 | 'multipart/form-data', 70 | 'multipart/alternative'])) { 71 | 72 | // reset values 73 | data.postData.mimeType = 'multipart/form-data' 74 | } 75 | 76 | else if (some([ 77 | 'application/x-www-form-urlencoded'])) { 78 | 79 | if (!data.postData.params) { 80 | data.postData.text = '' 81 | } else { 82 | data.postData.paramsObj = data.postData.params.reduce(reducer, {}) 83 | 84 | // always overwrite 85 | data.postData.text = qs.stringify(data.postData.paramsObj) 86 | } 87 | } 88 | 89 | else if (some([ 90 | 'text/json', 91 | 'text/x-json', 92 | 'application/json', 93 | 'application/x-json'])) { 94 | 95 | data.postData.mimeType = 'application/json' 96 | 97 | if (data.postData.text) { 98 | try { 99 | data.postData.jsonObj = JSON.parse(data.postData.text) 100 | } catch (e) { 101 | 102 | // force back to text/plain 103 | data.postData.mimeType = 'text/plain' 104 | } 105 | } 106 | } 107 | 108 | return data 109 | } 110 | 111 | function options (options) { 112 | // skip if no har property defined 113 | if (!options.har) { 114 | return options 115 | } 116 | 117 | var har = util._extend({}, options.har) 118 | 119 | // only process the first entry 120 | if (har.log && har.log.entries) { 121 | har = har.log.entries[0] 122 | } 123 | 124 | // add optional properties to make validation successful 125 | har.url = har.url || options.url || options.uri || options.baseUrl || '/' 126 | har.httpVersion = har.httpVersion || 'HTTP/1.1' 127 | har.queryString = har.queryString || [] 128 | har.headers = har.headers || [] 129 | har.cookies = har.cookies || [] 130 | har.postData = har.postData || {} 131 | har.postData.mimeType = har.postData.mimeType || 'application/octet-stream' 132 | 133 | har.bodySize = 0 134 | har.headersSize = 0 135 | har.postData.size = 0 136 | 137 | if (!validate.request(har)) { 138 | return options 139 | } 140 | 141 | // clean up and get some utility properties 142 | var req = prep(har) 143 | 144 | // construct new options 145 | if (req.url) { 146 | options.url = req.url 147 | } 148 | 149 | if (req.method) { 150 | options.method = req.method 151 | } 152 | 153 | if (Object.keys(req.queryObj).length) { 154 | options.qs = req.queryObj 155 | } 156 | 157 | if (Object.keys(req.headersObj).length) { 158 | options.headers = req.headersObj 159 | } 160 | 161 | function test (type) { 162 | return req.postData.mimeType.indexOf(type) === 0 163 | } 164 | if (test('application/x-www-form-urlencoded')) { 165 | options.form = req.postData.paramsObj 166 | } 167 | else if (test('application/json')) { 168 | if (req.postData.jsonObj) { 169 | options.body = req.postData.jsonObj 170 | options.json = true 171 | } 172 | } 173 | else if (test('multipart/form-data')) { 174 | options.formData = {} 175 | 176 | req.postData.params.forEach(function (param) { 177 | var attachment = {} 178 | 179 | if (!param.fileName && !param.fileName && !param.contentType) { 180 | options.formData[param.name] = param.value 181 | return 182 | } 183 | 184 | // attempt to read from disk! 185 | if (param.fileName && !param.value) { 186 | attachment.value = fs.createReadStream(param.fileName) 187 | } else if (param.value) { 188 | attachment.value = param.value 189 | } 190 | 191 | if (param.fileName) { 192 | attachment.options = { 193 | filename: param.fileName, 194 | contentType: param.contentType ? param.contentType : null 195 | } 196 | } 197 | 198 | options.formData[param.name] = attachment 199 | }) 200 | } 201 | else { 202 | if (req.postData.text) { 203 | options.body = req.postData.text 204 | } 205 | } 206 | 207 | return options 208 | } 209 | 210 | module.exports = options 211 | -------------------------------------------------------------------------------- /tests/test-basic-auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var assert = require('assert') 4 | , http = require('http') 5 | , request = require('../index') 6 | , tape = require('tape') 7 | 8 | var numBasicRequests = 0 9 | , basicServer 10 | , port = 6767 11 | 12 | tape('setup', function(t) { 13 | basicServer = http.createServer(function (req, res) { 14 | numBasicRequests++ 15 | 16 | var ok 17 | 18 | if (req.headers.authorization) { 19 | if (req.headers.authorization === 'Basic ' + new Buffer('user:pass').toString('base64')) { 20 | ok = true 21 | } else if ( req.headers.authorization === 'Basic ' + new Buffer('user:').toString('base64')) { 22 | ok = true 23 | } else if ( req.headers.authorization === 'Basic ' + new Buffer(':pass').toString('base64')) { 24 | ok = true 25 | } else if ( req.headers.authorization === 'Basic ' + new Buffer('user:pâss').toString('base64')) { 26 | ok = true 27 | } else { 28 | // Bad auth header, don't send back WWW-Authenticate header 29 | ok = false 30 | } 31 | } else { 32 | // No auth header, send back WWW-Authenticate header 33 | ok = false 34 | res.setHeader('www-authenticate', 'Basic realm="Private"') 35 | } 36 | 37 | if (req.url === '/post/') { 38 | var expectedContent = 'key=value' 39 | req.on('data', function(data) { 40 | assert.equal(data, expectedContent) 41 | }) 42 | assert.equal(req.method, 'POST') 43 | assert.equal(req.headers['content-length'], '' + expectedContent.length) 44 | assert.equal(req.headers['content-type'], 'application/x-www-form-urlencoded') 45 | } 46 | 47 | if (ok) { 48 | res.end('ok') 49 | } else { 50 | res.statusCode = 401 51 | res.end('401') 52 | } 53 | }).listen(port, function() { 54 | t.end() 55 | }) 56 | }) 57 | 58 | tape('sendImmediately - false', function(t) { 59 | var r = request({ 60 | 'method': 'GET', 61 | 'uri': 'http://localhost:6767/test/', 62 | 'auth': { 63 | 'user': 'user', 64 | 'pass': 'pass', 65 | 'sendImmediately': false 66 | } 67 | }, function(error, res, body) { 68 | t.equal(r._auth.user, 'user') 69 | t.equal(res.statusCode, 200) 70 | t.equal(numBasicRequests, 2) 71 | t.end() 72 | }) 73 | }) 74 | 75 | tape('sendImmediately - true', function(t) { 76 | // If we don't set sendImmediately = false, request will send basic auth 77 | var r = request({ 78 | 'method': 'GET', 79 | 'uri': 'http://localhost:6767/test2/', 80 | 'auth': { 81 | 'user': 'user', 82 | 'pass': 'pass' 83 | } 84 | }, function(error, res, body) { 85 | t.equal(r._auth.user, 'user') 86 | t.equal(res.statusCode, 200) 87 | t.equal(numBasicRequests, 3) 88 | t.end() 89 | }) 90 | }) 91 | 92 | tape('credentials in url', function(t) { 93 | var r = request({ 94 | 'method': 'GET', 95 | 'uri': 'http://user:pass@localhost:6767/test2/' 96 | }, function(error, res, body) { 97 | t.equal(r._auth.user, 'user') 98 | t.equal(res.statusCode, 200) 99 | t.equal(numBasicRequests, 4) 100 | t.end() 101 | }) 102 | }) 103 | 104 | tape('POST request', function(t) { 105 | var r = request({ 106 | 'method': 'POST', 107 | 'form': { 'key': 'value' }, 108 | 'uri': 'http://localhost:6767/post/', 109 | 'auth': { 110 | 'user': 'user', 111 | 'pass': 'pass', 112 | 'sendImmediately': false 113 | } 114 | }, function(error, res, body) { 115 | t.equal(r._auth.user, 'user') 116 | t.equal(res.statusCode, 200) 117 | t.equal(numBasicRequests, 6) 118 | t.end() 119 | }) 120 | }) 121 | 122 | tape('user - empty string', function(t) { 123 | t.doesNotThrow( function() { 124 | var r = request({ 125 | 'method': 'GET', 126 | 'uri': 'http://localhost:6767/allow_empty_user/', 127 | 'auth': { 128 | 'user': '', 129 | 'pass': 'pass', 130 | 'sendImmediately': false 131 | } 132 | }, function(error, res, body ) { 133 | t.equal(r._auth.user, '') 134 | t.equal(res.statusCode, 200) 135 | t.equal(numBasicRequests, 8) 136 | t.end() 137 | }) 138 | }) 139 | }) 140 | 141 | tape('pass - undefined', function(t) { 142 | t.doesNotThrow( function() { 143 | var r = request({ 144 | 'method': 'GET', 145 | 'uri': 'http://localhost:6767/allow_undefined_password/', 146 | 'auth': { 147 | 'user': 'user', 148 | 'pass': undefined, 149 | 'sendImmediately': false 150 | } 151 | }, function(error, res, body ) { 152 | t.equal(r._auth.user, 'user') 153 | t.equal(res.statusCode, 200) 154 | t.equal(numBasicRequests, 10) 155 | t.end() 156 | }) 157 | }) 158 | }) 159 | 160 | 161 | tape('pass - utf8', function(t) { 162 | t.doesNotThrow( function() { 163 | var r = request({ 164 | 'method': 'GET', 165 | 'uri': 'http://localhost:6767/allow_undefined_password/', 166 | 'auth': { 167 | 'user': 'user', 168 | 'pass': 'pâss', 169 | 'sendImmediately': false 170 | } 171 | }, function(error, res, body ) { 172 | t.equal(r._auth.user, 'user') 173 | t.equal(r._auth.pass, 'pâss') 174 | t.equal(res.statusCode, 200) 175 | t.equal(numBasicRequests, 12) 176 | t.end() 177 | }) 178 | }) 179 | }) 180 | 181 | tape('auth method', function(t) { 182 | var r = request 183 | .get('http://localhost:6767/test/') 184 | .auth('user', '', false) 185 | .on('response', function (res) { 186 | t.equal(r._auth.user, 'user') 187 | t.equal(res.statusCode, 200) 188 | t.equal(numBasicRequests, 14) 189 | t.end() 190 | }) 191 | }) 192 | 193 | tape('get method', function(t) { 194 | var r = request.get('http://localhost:6767/test/', 195 | { 196 | auth: { 197 | user: 'user', 198 | pass: '', 199 | sendImmediately: false 200 | } 201 | }, function (err, res) { 202 | t.equal(r._auth.user, 'user') 203 | t.equal(err, null) 204 | t.equal(res.statusCode, 200) 205 | t.equal(numBasicRequests, 16) 206 | t.end() 207 | }) 208 | }) 209 | 210 | tape('cleanup', function(t) { 211 | basicServer.close(function() { 212 | t.end() 213 | }) 214 | }) 215 | -------------------------------------------------------------------------------- /tests/core.js: -------------------------------------------------------------------------------- 1 | 2 | var path = require('path') 3 | var spawn = require('child_process').spawn 4 | var minimist = require('minimist') 5 | 6 | var argv = minimist(process.argv.slice(2)) 7 | 8 | 9 | var suite = [ 10 | {test: 'agent', excluded: true}, 11 | {test: 'agentOptions', excluded: true}, 12 | {test: 'baseUrl', exclude: 4}, 13 | {test: 'basic-auth'}, 14 | {test: 'bearer-auth', exclude: 2}, 15 | {test: 'body'}, 16 | {test: 'cookies'}, 17 | {test: 'defaults'}, 18 | {test: 'digest-auth'}, 19 | {test: 'emptyBody', exclude: 1}, 20 | {test: 'errors', excluded: true}, 21 | {test: 'event-forwarding'}, 22 | {test: 'follow-all-303'}, 23 | {test: 'follow-all'}, 24 | {test: 'form-data-error', excluded: true}, 25 | {test: 'form-data'}, 26 | {test: 'form-urlencoded'}, 27 | {test: 'form', excluded: true}, 28 | {test: 'gzip', exclude: 1}, 29 | {test: 'har'}, 30 | {test: 'hawk'}, 31 | {test: 'headers'}, 32 | {test: 'http-signature'}, 33 | {test: 'httpModule', excluded: true}, 34 | {test: 'https'}, 35 | {test: 'isUrl', exclude: 1}, 36 | {test: 'json-request'}, 37 | {test: 'localAddress'}, 38 | {test: 'multipart-encoding', excluded: true}, 39 | {test: 'multipart'}, 40 | {test: 'node-debug', excluded: true}, 41 | {test: 'oauth'}, 42 | {test: 'onelineproxy'}, 43 | {test: 'option-reuse'}, 44 | {test: 'params'}, 45 | {test: 'piped-redirect'}, 46 | {test: 'pipes', exclude: 2}, 47 | {test: 'pool', excluded: true}, 48 | {test: 'promise'}, 49 | {test: 'proxy-connect'}, 50 | {test: 'proxy'}, 51 | {test: 'qs'}, 52 | {test: 'redirect-auth'}, 53 | {test: 'redirect-complex'}, 54 | {test: 'redirect'}, 55 | {test: 'rfc3986'}, 56 | {test: 'timeout'}, 57 | {test: 'timing'}, 58 | {test: 'toJSON'}, 59 | {test: 'tunnel'}, 60 | {test: 'unix'} 61 | ] 62 | 63 | 64 | var output = { 65 | cleanup: function (file, tests) { 66 | var fs = require('fs') 67 | var code = fs.readFileSync(file, 'utf8') 68 | tests.forEach(function (test) { 69 | code = code.replace(test.regex, test.end) 70 | }) 71 | eval(code) 72 | }, 73 | tape: function () { 74 | suite.forEach(function (entry) { 75 | var file = path.join(__dirname, 'test-' + entry.test + '.js') 76 | 77 | // options.agent doesn't work because in request's callback 78 | // the underlying socket is still kept as active in agent.sockets 79 | // where using the core's http module or @request/core the underlying socket 80 | // is already freed and ready to reuse in agent.freeSockets 81 | // 82 | // the next two tests are about the forever option and method 83 | // that are deprecated 84 | if (entry.test === 'agent') { 85 | return 86 | } 87 | 88 | // two tests about agentOptions and pool 89 | else if (entry.test === 'agentOptions') { 90 | return 91 | } 92 | 93 | // exclude the 4 tests that's expected to throw an error 94 | else if (entry.test === 'baseUrl') { 95 | output.cleanup(file, [ 96 | {regex: /tape\('error when baseUrl[\s\S]+tape\('cleanup/, end: 'tape(\'cleanup'} 97 | ]) 98 | } 99 | 100 | // 2 tests are skipped for auth.bearer = undefined | null 101 | else if (entry.test === 'bearer-auth') { 102 | output.cleanup(file, [ 103 | {regex: /tape\('no auth method'[\s\S]+tape\('cleanup/, end: 'tape(\'cleanup'} 104 | ]) 105 | } 106 | 107 | // 1 test expects body to be undefined if JSON.parse fails 108 | // @request/core is returning the original string body instead 109 | else if (entry.test === 'emptyBody') { 110 | output.cleanup(file, [ 111 | {regex: /tape\('empty JSON body'[\s\S]+tape\('cleanup/, end: 'tape(\'cleanup'} 112 | ]) 113 | } 114 | 115 | // not implemented error messages 116 | else if (entry.test === 'errors') { 117 | return 118 | } 119 | 120 | // uses not implemented form-data methods - append and getLength 121 | else if (entry.test === 'form') { 122 | return 123 | } 124 | else if (entry.test === 'form-data-error') { 125 | return 126 | } 127 | 128 | // 1 test expects a string chunk inside the on.data event 129 | // but that's a bad idea when useing iconv-lite 130 | // https://github.com/ashtuchkin/iconv-lite/wiki/Use-Buffers-when-decoding 131 | else if (entry.test === 'gzip') { 132 | output.cleanup(file, [ 133 | {regex: /tape\('supports character encoding with gzip encoding'[\s\S]+tape\('transparently supports gzip error to callbacks/, end: 'tape(\'transparently supports gzip error to callbacks'} 134 | ]) 135 | } 136 | 137 | // not implement feature in @request/legacy 138 | // tests about non documented httpModules option 139 | // https://github.com/request/request/pull/112 140 | else if (entry.test === 'httpModule') { 141 | return 142 | } 143 | 144 | // 1 test fails - options.uri is a required argument 145 | else if (entry.test === 'isUrl') { 146 | output.cleanup(file, [ 147 | {regex: /tape\('hostname and port 3[\s\S]+tape\('hostname and query string/, end: 'tape(\'hostname and query string'} 148 | ]) 149 | } 150 | 151 | // non implemented multipart API 152 | else if (entry.test === 'multipart-encoding') { 153 | return 154 | } 155 | 156 | // @request/log is used instead 157 | else if (entry.test === 'node-debug') { 158 | return 159 | } 160 | 161 | else if (entry.test === 'pipes') { 162 | output.cleanup(file, [ 163 | // piping from a file sets the content-length - I think that's correct 164 | {regex: /testPipeFromFile\('piping from a file'[\s\S]+testPipeFromFile/, end: 'testPipeFromFile'}, 165 | // no such error handling 166 | {regex: /tape\('piping to a request object with invalid uri'[\s\S]+tape\('piping to a request object with a json body/, end: 'tape(\'piping to a request object with a json body'} 167 | ]) 168 | } 169 | 170 | // mostly about the non supported forever-agent 171 | else if (entry.test === 'pool') { 172 | return 173 | } 174 | 175 | // ok 176 | else { 177 | require(file) 178 | } 179 | }) 180 | }, 181 | taper: function () { 182 | var files = suite.map(function (entry) { 183 | return path.join(__dirname, 'test-' + entry.test + '.js') 184 | }) 185 | 186 | var taper = spawn('taper', files) 187 | taper.stdout.pipe(process.stdout) 188 | } 189 | } 190 | 191 | 192 | if (argv.m == 'taper') { 193 | output.taper() 194 | } 195 | else if (argv.m == 'tape') { 196 | output.tape() 197 | } 198 | else { 199 | output.taper() 200 | } 201 | -------------------------------------------------------------------------------- /tests/test-gzip.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var request = require('../index') 4 | , http = require('http') 5 | , zlib = require('zlib') 6 | , assert = require('assert') 7 | , bufferEqual = require('buffer-equal') 8 | , tape = require('tape') 9 | 10 | var testContent = 'Compressible response content.\n' 11 | , testContentBig 12 | , testContentBigGzip 13 | , testContentGzip 14 | 15 | var server = http.createServer(function(req, res) { 16 | res.statusCode = 200 17 | res.setHeader('Content-Type', 'text/plain') 18 | 19 | if (/\bgzip\b/i.test(req.headers['accept-encoding'])) { 20 | res.setHeader('Content-Encoding', 'gzip') 21 | if (req.url === '/error') { 22 | // send plaintext instead of gzip (should cause an error for the client) 23 | res.end(testContent) 24 | } else if (req.url === '/chunks') { 25 | res.writeHead(200) 26 | res.write(testContentBigGzip.slice(0, 4096)) 27 | setTimeout(function() { res.end(testContentBigGzip.slice(4096)) }, 10) 28 | } else { 29 | zlib.gzip(testContent, function(err, data) { 30 | assert.equal(err, null) 31 | res.end(data) 32 | }) 33 | } 34 | } else { 35 | res.end(testContent) 36 | } 37 | }) 38 | 39 | tape('setup', function(t) { 40 | // Need big compressed content to be large enough to chunk into gzip blocks. 41 | // Want it to be deterministic to ensure test is reliable. 42 | // Generate pseudo-random printable ASCII characters using MINSTD 43 | var a = 48271 44 | , m = 0x7FFFFFFF 45 | , x = 1 46 | testContentBig = new Buffer(10240) 47 | for (var i = 0; i < testContentBig.length; ++i) { 48 | x = (a * x) & m 49 | // Printable ASCII range from 32-126, inclusive 50 | testContentBig[i] = (x % 95) + 32 51 | } 52 | 53 | zlib.gzip(testContent, function(err, data) { 54 | t.equal(err, null) 55 | testContentGzip = data 56 | 57 | zlib.gzip(testContentBig, function(err, data2) { 58 | t.equal(err, null) 59 | testContentBigGzip = data2 60 | 61 | server.listen(6767, function() { 62 | t.end() 63 | }) 64 | }) 65 | }) 66 | }) 67 | 68 | tape('transparently supports gzip decoding to callbacks', function(t) { 69 | var options = { url: 'http://localhost:6767/foo', gzip: true } 70 | request.get(options, function(err, res, body) { 71 | t.equal(err, null) 72 | t.equal(res.headers['content-encoding'], 'gzip') 73 | t.equal(body, testContent) 74 | t.end() 75 | }) 76 | }) 77 | 78 | tape('transparently supports gzip decoding to pipes', function(t) { 79 | var options = { url: 'http://localhost:6767/foo', gzip: true } 80 | var chunks = [] 81 | request.get(options) 82 | .on('data', function(chunk) { 83 | chunks.push(chunk) 84 | }) 85 | .on('end', function() { 86 | t.equal(Buffer.concat(chunks).toString(), testContent) 87 | t.end() 88 | }) 89 | .on('error', function(err) { 90 | t.fail(err) 91 | }) 92 | }) 93 | 94 | tape('does not request gzip if user specifies Accepted-Encodings', function(t) { 95 | var headers = { 'Accept-Encoding': null } 96 | var options = { 97 | url: 'http://localhost:6767/foo', 98 | headers: headers, 99 | gzip: true 100 | } 101 | request.get(options, function(err, res, body) { 102 | t.equal(err, null) 103 | t.equal(res.headers['content-encoding'], undefined) 104 | t.equal(body, testContent) 105 | t.end() 106 | }) 107 | }) 108 | 109 | tape('does not decode user-requested encoding by default', function(t) { 110 | var headers = { 'Accept-Encoding': 'gzip' } 111 | var options = { url: 'http://localhost:6767/foo', headers: headers } 112 | request.get(options, function(err, res, body) { 113 | t.equal(err, null) 114 | t.equal(res.headers['content-encoding'], 'gzip') 115 | t.equal(body, testContentGzip.toString()) 116 | t.end() 117 | }) 118 | }) 119 | 120 | tape('supports character encoding with gzip encoding', function(t) { 121 | var headers = { 'Accept-Encoding': 'gzip' } 122 | var options = { 123 | url: 'http://localhost:6767/foo', 124 | headers: headers, 125 | gzip: true, 126 | encoding: 'utf8' 127 | } 128 | var strings = [] 129 | request.get(options) 130 | .on('data', function(string) { 131 | t.equal(typeof string, 'string') 132 | strings.push(string) 133 | }) 134 | .on('end', function() { 135 | t.equal(strings.join(''), testContent) 136 | t.end() 137 | }) 138 | .on('error', function(err) { 139 | t.fail(err) 140 | }) 141 | }) 142 | 143 | tape('transparently supports gzip error to callbacks', function(t) { 144 | var options = { url: 'http://localhost:6767/error', gzip: true } 145 | request.get(options, function(err, res, body) { 146 | t.equal(err.code, 'Z_DATA_ERROR') 147 | t.equal(res, undefined) 148 | t.equal(body, undefined) 149 | t.end() 150 | }) 151 | }) 152 | 153 | tape('transparently supports gzip error to pipes', function(t) { 154 | var options = { url: 'http://localhost:6767/error', gzip: true } 155 | request.get(options) 156 | .on('data', function (/*chunk*/) { 157 | t.fail('Should not receive data event') 158 | }) 159 | .on('end', function () { 160 | t.fail('Should not receive end event') 161 | }) 162 | .on('error', function (err) { 163 | t.equal(err.code, 'Z_DATA_ERROR') 164 | t.end() 165 | }) 166 | }) 167 | 168 | tape('pause when streaming from a gzip request object', function(t) { 169 | var chunks = [] 170 | var paused = false 171 | var options = { url: 'http://localhost:6767/chunks', gzip: true } 172 | request.get(options) 173 | .on('data', function(chunk) { 174 | var self = this 175 | 176 | t.notOk(paused, 'Only receive data when not paused') 177 | 178 | chunks.push(chunk) 179 | if (chunks.length === 1) { 180 | self.pause() 181 | paused = true 182 | setTimeout(function() { 183 | paused = false 184 | self.resume() 185 | }, 100) 186 | } 187 | }) 188 | .on('end', function() { 189 | t.ok(chunks.length > 1, 'Received multiple chunks') 190 | t.ok(bufferEqual(Buffer.concat(chunks), testContentBig), 'Expected content') 191 | t.end() 192 | }) 193 | }) 194 | 195 | tape('pause before streaming from a gzip request object', function(t) { 196 | var paused = true 197 | var options = { url: 'http://localhost:6767/foo', gzip: true } 198 | var r = request.get(options) 199 | r.pause() 200 | r.on('data', function(data) { 201 | t.notOk(paused, 'Only receive data when not paused') 202 | t.equal(data.toString(), testContent) 203 | }) 204 | r.on('end', t.end.bind(t)) 205 | 206 | setTimeout(function() { 207 | paused = false 208 | r.resume() 209 | }, 100) 210 | }) 211 | 212 | tape('cleanup', function(t) { 213 | server.close(function() { 214 | t.end() 215 | }) 216 | }) 217 | -------------------------------------------------------------------------------- /tests/test-digest-auth.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var http = require('http') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | , crypto = require('crypto') 7 | 8 | function makeHeader() { 9 | return [].join.call(arguments, ', ') 10 | } 11 | 12 | function makeHeaderRegex() { 13 | return new RegExp('^' + makeHeader.apply(null, arguments) + '$') 14 | } 15 | 16 | function md5 (str) { 17 | return crypto.createHash('md5').update(str).digest('hex') 18 | } 19 | 20 | var digestServer = http.createServer(function(req, res) { 21 | var ok 22 | , testHeader 23 | 24 | if (req.url === '/test/') { 25 | if (req.headers.authorization) { 26 | testHeader = makeHeaderRegex( 27 | 'Digest username="test"', 28 | 'realm="Private"', 29 | 'nonce="WpcHS2/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93"', 30 | 'uri="/test/"', 31 | 'qop=auth', 32 | 'response="[a-f0-9]{32}"', 33 | 'nc=00000001', 34 | 'cnonce="[a-f0-9]{32}"', 35 | 'algorithm=MD5', 36 | 'opaque="5ccc069c403ebaf9f0171e9517f40e41"' 37 | ) 38 | if (testHeader.test(req.headers.authorization)) { 39 | ok = true 40 | } else { 41 | // Bad auth header, don't send back WWW-Authenticate header 42 | ok = false 43 | } 44 | } else { 45 | // No auth header, send back WWW-Authenticate header 46 | ok = false 47 | res.setHeader('www-authenticate', makeHeader( 48 | 'Digest realm="Private"', 49 | 'nonce="WpcHS2/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93"', 50 | 'algorithm=MD5', 51 | 'qop="auth"', 52 | 'opaque="5ccc069c403ebaf9f0171e9517f40e41"' 53 | )) 54 | } 55 | } else if (req.url === '/test/md5-sess') { // RFC 2716 MD5-sess w/ qop=auth 56 | var user = 'test' 57 | var realm = 'Private' 58 | var pass = 'testing' 59 | var nonce = 'WpcHS2/TBAA=dffcc0dbd5f96d49a5477166649b7c0ae3866a93' 60 | var nonceCount = '00000001' 61 | var qop = 'auth' 62 | var algorithm = 'MD5-sess' 63 | if (req.headers.authorization) { 64 | 65 | //HA1=MD5(MD5(username:realm:password):nonce:cnonce) 66 | //HA2=MD5(method:digestURI) 67 | //response=MD5(HA1:nonce:nonceCount:clientNonce:qop:HA2) 68 | 69 | var cnonce = /cnonce="(.*)"/.exec(req.headers.authorization)[1] 70 | var ha1 = md5(md5(user + ':' + realm + ':' + pass) + ':' + nonce + ':' + cnonce) 71 | var ha2 = md5('GET:/test/md5-sess') 72 | var response = md5(ha1 + ':' + nonce + ':' + nonceCount + ':' + cnonce + ':' + qop + ':' + ha2) 73 | 74 | testHeader = makeHeaderRegex( 75 | 'Digest username="' + user + '"', 76 | 'realm="' + realm + '"', 77 | 'nonce="' + nonce + '"', 78 | 'uri="/test/md5-sess"', 79 | 'qop=' + qop, 80 | 'response="' + response + '"', 81 | 'nc=' + nonceCount, 82 | 'cnonce="' + cnonce + '"', 83 | 'algorithm=' + algorithm 84 | ) 85 | 86 | ok = testHeader.test(req.headers.authorization) 87 | } else { 88 | // No auth header, send back WWW-Authenticate header 89 | ok = false 90 | res.setHeader('www-authenticate', makeHeader( 91 | 'Digest realm="' + realm + '"', 92 | 'nonce="' + nonce + '"', 93 | 'algorithm=' + algorithm, 94 | 'qop="' + qop + '"' 95 | )) 96 | } 97 | } else if (req.url === '/dir/index.html') { 98 | // RFC2069-compatible mode 99 | // check: http://www.rfc-editor.org/errata_search.php?rfc=2069 100 | if (req.headers.authorization) { 101 | testHeader = makeHeaderRegex( 102 | 'Digest username="Mufasa"', 103 | 'realm="testrealm@host.com"', 104 | 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"', 105 | 'uri="/dir/index.html"', 106 | 'response="[a-f0-9]{32}"', 107 | 'opaque="5ccc069c403ebaf9f0171e9517f40e41"' 108 | ) 109 | if (testHeader.test(req.headers.authorization)) { 110 | ok = true 111 | } else { 112 | // Bad auth header, don't send back WWW-Authenticate header 113 | ok = false 114 | } 115 | } else { 116 | // No auth header, send back WWW-Authenticate header 117 | ok = false 118 | res.setHeader('www-authenticate', makeHeader( 119 | 'Digest realm="testrealm@host.com"', 120 | 'nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093"', 121 | 'opaque="5ccc069c403ebaf9f0171e9517f40e41"' 122 | )) 123 | } 124 | } 125 | 126 | if (ok) { 127 | res.end('ok') 128 | } else { 129 | res.statusCode = 401 130 | res.end('401') 131 | } 132 | }) 133 | 134 | tape('setup', function(t) { 135 | digestServer.listen(6767, function() { 136 | t.end() 137 | }) 138 | }) 139 | 140 | tape('with sendImmediately = false', function(t) { 141 | var numRedirects = 0 142 | 143 | request({ 144 | method: 'GET', 145 | uri: 'http://localhost:6767/test/', 146 | auth: { 147 | user: 'test', 148 | pass: 'testing', 149 | sendImmediately: false 150 | } 151 | }, function(error, response, body) { 152 | t.equal(error, null) 153 | t.equal(response.statusCode, 200) 154 | t.equal(numRedirects, 1) 155 | t.end() 156 | }).on('redirect', function() { 157 | t.equal(this.response.statusCode, 401) 158 | numRedirects++ 159 | }) 160 | }) 161 | 162 | tape('with MD5-sess algorithm', function(t) { 163 | var numRedirects = 0 164 | 165 | request({ 166 | method: 'GET', 167 | uri: 'http://localhost:6767/test/md5-sess', 168 | auth: { 169 | user: 'test', 170 | pass: 'testing', 171 | sendImmediately: false 172 | } 173 | }, function(error, response, body) { 174 | t.equal(error, null) 175 | t.equal(response.statusCode, 200) 176 | t.equal(numRedirects, 1) 177 | t.end() 178 | }).on('redirect', function() { 179 | t.equal(this.response.statusCode, 401) 180 | numRedirects++ 181 | }) 182 | }) 183 | 184 | tape('without sendImmediately = false', function(t) { 185 | var numRedirects = 0 186 | 187 | // If we don't set sendImmediately = false, request will send basic auth 188 | request({ 189 | method: 'GET', 190 | uri: 'http://localhost:6767/test/', 191 | auth: { 192 | user: 'test', 193 | pass: 'testing' 194 | } 195 | }, function(error, response, body) { 196 | t.equal(error, null) 197 | t.equal(response.statusCode, 401) 198 | t.equal(numRedirects, 0) 199 | t.end() 200 | }).on('redirect', function() { 201 | t.equal(this.response.statusCode, 401) 202 | numRedirects++ 203 | }) 204 | }) 205 | 206 | tape('with different credentials', function(t) { 207 | var numRedirects = 0 208 | 209 | request({ 210 | method: 'GET', 211 | uri: 'http://localhost:6767/dir/index.html', 212 | auth: { 213 | user: 'Mufasa', 214 | pass: 'CircleOfLife', 215 | sendImmediately: false 216 | } 217 | }, function(error, response, body) { 218 | t.equal(error, null) 219 | t.equal(response.statusCode, 200) 220 | t.equal(numRedirects, 1) 221 | t.end() 222 | }).on('redirect', function() { 223 | t.equal(this.response.statusCode, 401) 224 | numRedirects++ 225 | }) 226 | }) 227 | 228 | tape('cleanup', function(t) { 229 | digestServer.close(function() { 230 | t.end() 231 | }) 232 | }) 233 | -------------------------------------------------------------------------------- /tests/test-proxy.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var server = require('./server') 4 | , request = require('../index') 5 | , tape = require('tape') 6 | 7 | var s = server.createServer() 8 | , currResponseHandler 9 | 10 | ['http://google.com/', 'https://google.com/'].forEach(function(url) { 11 | s.on(url, function(req, res) { 12 | currResponseHandler(req, res) 13 | res.writeHeader(200) 14 | res.end('ok') 15 | }) 16 | }) 17 | 18 | var proxyEnvVars = [ 19 | 'http_proxy', 20 | 'HTTP_PROXY', 21 | 'https_proxy', 22 | 'HTTPS_PROXY', 23 | 'no_proxy', 24 | 'NO_PROXY' 25 | ] 26 | 27 | // Set up and run a proxy test. All environment variables pertaining to 28 | // proxies will be deleted before each test. Specify environment variables as 29 | // `options.env`; all other keys on `options` will be passed as additional 30 | // options to `request`. 31 | // 32 | // If `responseHandler` is a function, it should perform asserts on the server 33 | // response. It will be called with parameters (t, req, res). Otherwise, 34 | // `responseHandler` should be truthy to indicate that the proxy should be used 35 | // for this request, or falsy to indicate that the proxy should not be used for 36 | // this request. 37 | function runTest(name, options, responseHandler) { 38 | tape(name, function(t) { 39 | proxyEnvVars.forEach(function(v) { 40 | delete process.env[v] 41 | }) 42 | if (options.env) { 43 | for (var v in options.env) { 44 | process.env[v] = options.env[v] 45 | } 46 | delete options.env 47 | } 48 | 49 | var called = false 50 | currResponseHandler = function(req, res) { 51 | if (responseHandler) { 52 | called = true 53 | t.equal(req.headers.host, 'google.com') 54 | if (typeof responseHandler === 'function') { 55 | responseHandler(t, req, res) 56 | } 57 | } else { 58 | t.fail('proxy response should not be called') 59 | } 60 | } 61 | 62 | options.url = options.url || 'http://google.com' 63 | request(options, function(err, res, body) { 64 | if (responseHandler && !called) { 65 | t.fail('proxy response should be called') 66 | } 67 | t.equal(err, null) 68 | t.equal(res.statusCode, 200) 69 | if (responseHandler) { 70 | if (body.length > 100) { 71 | body = body.substring(0, 100) 72 | } 73 | t.equal(body, 'ok') 74 | } else { 75 | t.equal(/^/i.test(body), true) 76 | } 77 | t.end() 78 | }) 79 | }) 80 | } 81 | 82 | tape('setup', function(t) { 83 | s.listen(s.port, function() { 84 | t.end() 85 | }) 86 | }) 87 | 88 | 89 | // If the `runTest` function is changed, run the following command and make 90 | // sure both of these tests fail: 91 | // 92 | // TEST_PROXY_HARNESS=y node tests/test-proxy.js 93 | 94 | if (process.env.TEST_PROXY_HARNESS) { 95 | 96 | runTest('should fail with "proxy response should not be called"', { 97 | proxy : s.url 98 | }, false) 99 | 100 | runTest('should fail with "proxy response should be called"', { 101 | proxy : null 102 | }, true) 103 | 104 | } else { 105 | // Run the real tests 106 | 107 | runTest('basic proxy', { 108 | proxy : s.url, 109 | headers : { 110 | 'proxy-authorization': 'Token Fooblez' 111 | } 112 | }, function(t, req, res) { 113 | t.equal(req.headers['proxy-authorization'], 'Token Fooblez') 114 | }) 115 | 116 | runTest('proxy auth without uri auth', { 117 | proxy : 'http://user:pass@localhost:' + s.port 118 | }, function(t, req, res) { 119 | t.equal(req.headers['proxy-authorization'], 'Basic dXNlcjpwYXNz') 120 | }) 121 | 122 | // http: urls and basic proxy settings 123 | 124 | runTest('HTTP_PROXY environment variable and http: url', { 125 | env : { HTTP_PROXY : s.url } 126 | }, true) 127 | 128 | runTest('http_proxy environment variable and http: url', { 129 | env : { http_proxy : s.url } 130 | }, true) 131 | 132 | runTest('HTTPS_PROXY environment variable and http: url', { 133 | env : { HTTPS_PROXY : s.url } 134 | }, false) 135 | 136 | runTest('https_proxy environment variable and http: url', { 137 | env : { https_proxy : s.url } 138 | }, false) 139 | 140 | // https: urls and basic proxy settings 141 | 142 | runTest('HTTP_PROXY environment variable and https: url', { 143 | env : { HTTP_PROXY : s.url }, 144 | url : 'https://google.com', 145 | tunnel : false, 146 | pool : false 147 | }, true) 148 | 149 | runTest('http_proxy environment variable and https: url', { 150 | env : { http_proxy : s.url }, 151 | url : 'https://google.com', 152 | tunnel : false 153 | }, true) 154 | 155 | runTest('HTTPS_PROXY environment variable and https: url', { 156 | env : { HTTPS_PROXY : s.url }, 157 | url : 'https://google.com', 158 | tunnel : false 159 | }, true) 160 | 161 | runTest('https_proxy environment variable and https: url', { 162 | env : { https_proxy : s.url }, 163 | url : 'https://google.com', 164 | tunnel : false 165 | }, true) 166 | 167 | runTest('multiple environment variables and https: url', { 168 | env : { 169 | HTTPS_PROXY : s.url, 170 | HTTP_PROXY : 'http://localhost:4/' 171 | }, 172 | url : 'https://google.com', 173 | tunnel : false 174 | }, true) 175 | 176 | // no_proxy logic 177 | 178 | runTest('NO_PROXY hostnames are case insensitive', { 179 | env : { 180 | HTTP_PROXY : s.url, 181 | NO_PROXY : 'GOOGLE.COM' 182 | } 183 | }, false) 184 | 185 | runTest('NO_PROXY hostnames are case insensitive 2', { 186 | env : { 187 | http_proxy : s.url, 188 | NO_PROXY : 'GOOGLE.COM' 189 | } 190 | }, false) 191 | 192 | runTest('NO_PROXY hostnames are case insensitive 3', { 193 | env : { 194 | HTTP_PROXY : s.url, 195 | no_proxy : 'GOOGLE.COM' 196 | } 197 | }, false) 198 | 199 | runTest('NO_PROXY ignored with explicit proxy passed', { 200 | env : { NO_PROXY : '*' }, 201 | proxy : s.url 202 | }, true) 203 | 204 | runTest('NO_PROXY overrides HTTP_PROXY for specific hostname', { 205 | env : { 206 | HTTP_PROXY : s.url, 207 | NO_PROXY : 'google.com' 208 | } 209 | }, false) 210 | 211 | runTest('no_proxy overrides HTTP_PROXY for specific hostname', { 212 | env : { 213 | HTTP_PROXY : s.url, 214 | no_proxy : 'google.com' 215 | } 216 | }, false) 217 | 218 | runTest('NO_PROXY does not override HTTP_PROXY if no hostnames match', { 219 | env : { 220 | HTTP_PROXY : s.url, 221 | NO_PROXY : 'foo.bar,bar.foo' 222 | } 223 | }, true) 224 | 225 | runTest('NO_PROXY overrides HTTP_PROXY if a hostname matches', { 226 | env : { 227 | HTTP_PROXY : s.url, 228 | NO_PROXY : 'foo.bar,google.com' 229 | } 230 | }, false) 231 | 232 | runTest('NO_PROXY allows an explicit port', { 233 | env : { 234 | HTTP_PROXY : s.url, 235 | NO_PROXY : 'google.com:80' 236 | } 237 | }, false) 238 | 239 | runTest('NO_PROXY only overrides HTTP_PROXY if the port matches', { 240 | env : { 241 | HTTP_PROXY : s.url, 242 | NO_PROXY : 'google.com:1234' 243 | } 244 | }, true) 245 | 246 | runTest('NO_PROXY=* should override HTTP_PROXY for all hosts', { 247 | env : { 248 | HTTP_PROXY : s.url, 249 | NO_PROXY : '*' 250 | } 251 | }, false) 252 | 253 | runTest('NO_PROXY should override HTTP_PROXY for all subdomains', { 254 | env : { 255 | HTTP_PROXY : s.url, 256 | NO_PROXY : 'google.com' 257 | }, 258 | headers : { host : 'www.google.com' } 259 | }, false) 260 | 261 | runTest('NO_PROXY should not override HTTP_PROXY for partial domain matches', { 262 | env : { 263 | HTTP_PROXY : s.url, 264 | NO_PROXY : 'oogle.com' 265 | } 266 | }, true) 267 | 268 | runTest('NO_PROXY with port should not override HTTP_PROXY for partial domain matches', { 269 | env : { 270 | HTTP_PROXY : s.url, 271 | NO_PROXY : 'oogle.com:80' 272 | } 273 | }, true) 274 | 275 | // misc 276 | 277 | // this fails if the check 'isMatchedAt > -1' in lib/getProxyFromURI.js is 278 | // missing or broken 279 | runTest('http_proxy with length of one more than the URL', { 280 | env : { 281 | HTTP_PROXY : s.url, 282 | NO_PROXY : 'elgoog1.com' // one more char than google.com 283 | } 284 | }, true) 285 | 286 | runTest('proxy: null should override HTTP_PROXY', { 287 | env : { HTTP_PROXY : s.url }, 288 | proxy : null, 289 | timeout : 500 290 | }, false) 291 | 292 | runTest('uri auth without proxy auth', { 293 | url : 'http://user:pass@google.com', 294 | proxy : s.url 295 | }, function(t, req, res) { 296 | t.equal(req.headers['proxy-authorization'], undefined) 297 | t.equal(req.headers.authorization, 'Basic dXNlcjpwYXNz') 298 | }) 299 | } 300 | 301 | 302 | tape('cleanup', function(t) { 303 | s.close(function() { 304 | t.end() 305 | }) 306 | }) 307 | --------------------------------------------------------------------------------