Artifact Content
Not logged in

Artifact a4d9076c5526803cf6b4202e6b558f1299bb2cb8:


'use strict'

var server = require('./server')
  , tape = require('tape')
  , request = require('../index')
  , https = require('https')
  , net = require('net')
  , fs = require('fs')
  , path = require('path')
  , util = require('util')
  , url = require('url')
  , destroyable = require('server-destroy')

var events = []
  , caFile = path.resolve(__dirname, 'ssl/ca/ca.crt')
  , ca = fs.readFileSync(caFile)
  , clientCert = fs.readFileSync(path.resolve(__dirname, 'ssl/ca/client.crt'))
  , clientKey = fs.readFileSync(path.resolve(__dirname, 'ssl/ca/client-enc.key'))
  , clientPassword = 'password'
  , sslOpts = {
    key  : path.resolve(__dirname, 'ssl/ca/localhost.key'),
    cert : path.resolve(__dirname, 'ssl/ca/localhost.crt')
  }
  , mutualSSLOpts = {
    key  : path.resolve(__dirname, 'ssl/ca/localhost.key'),
    cert : path.resolve(__dirname, 'ssl/ca/localhost.crt'),
    ca   : caFile,
    requestCert        : true,
    rejectUnauthorized : true
  }

// this is needed for 'https over http, tunnel=false' test
// from https://github.com/coolaj86/node-ssl-root-cas/blob/v1.1.9-beta/ssl-root-cas.js#L4267-L4281
var httpsOpts = https.globalAgent.options
httpsOpts.ca = httpsOpts.ca || []
httpsOpts.ca.push(ca)

var s = server.createServer()
  , ss = server.createSSLServer(null, sslOpts)
  , ss2 = server.createSSLServer(ss.port + 1, mutualSSLOpts)

// XXX when tunneling https over https, connections get left open so the server
// doesn't want to close normally (and same issue with http server on v0.8.x)
destroyable(s)
destroyable(ss)
destroyable(ss2)

function event() {
  events.push(util.format.apply(null, arguments))
}

function setListeners(server, type) {
  server.on('/', function(req, res) {
    event('%s response', type)
    res.end(type + ' ok')
  })

  server.on('request', function(req, res) {
    if (/^https?:/.test(req.url)) {
      // This is a proxy request
      var dest = req.url.split(':')[0]
      // Is it a redirect?
      var match = req.url.match(/\/redirect\/(https?)$/)
      if (match) {
        dest += '->' + match[1]
      }
      event('%s proxy to %s', type, dest)
      request(req.url, { followRedirect : false }).pipe(res)
    }
  })

  server.on('/redirect/http', function(req, res) {
    event('%s redirect to http', type)
    res.writeHead(301, {
      location : s.url
    })
    res.end()
  })

  server.on('/redirect/https', function(req, res) {
    event('%s redirect to https', type)
    res.writeHead(301, {
      location : ss.url
    })
    res.end()
  })

  server.on('connect', function(req, client, head) {
    var u = url.parse(req.url)
    var server = net.connect(u.host, u.port, function() {
      event('%s connect to %s', type, req.url)
      client.write('HTTP/1.1 200 Connection established\r\n\r\n')
      client.pipe(server)
      server.write(head)
      server.pipe(client)
    })
  })
}

setListeners(s, 'http')
setListeners(ss, 'https')
setListeners(ss2, 'https')

tape('setup', function(t) {
  s.listen(s.port, function() {
    ss.listen(ss.port, function() {
      ss2.listen(ss2.port, 'localhost', function() {
        t.end()
      })
    })
  })
})

// monkey-patch since you can't set a custom certificate authority for the
// proxy in tunnel-agent (this is necessary for "* over https" tests)
var customCaCount = 0
var httpsRequestOld = https.request
https.request = function(options) {
  if (customCaCount) {
    options.ca = ca
    customCaCount--
  }
  return httpsRequestOld.apply(this, arguments)
}

function runTest(name, opts, expected) {
  tape(name, function(t) {
    opts.ca = ca
    if (opts.proxy === ss.url) {
      customCaCount = (opts.url === ss.url ? 2 : 1)
    }
    request(opts, function(err, res, body) {
      event(err ? 'err ' + err.message : res.statusCode + ' ' + body)
      t.deepEqual(events, expected)
      events = []
      t.end()
    })
  })
}


// HTTP OVER HTTP

runTest('http over http, tunnel=true', {
  url    : s.url,
  proxy  : s.url,
  tunnel : true
}, [
  'http connect to localhost:' + s.port,
  'http response',
  '200 http ok'
])

runTest('http over http, tunnel=false', {
  url    : s.url,
  proxy  : s.url,
  tunnel : false
}, [
  'http proxy to http',
  'http response',
  '200 http ok'
])

runTest('http over http, tunnel=default', {
  url    : s.url,
  proxy  : s.url
}, [
  'http proxy to http',
  'http response',
  '200 http ok'
])


// HTTP OVER HTTPS

runTest('http over https, tunnel=true', {
  url    : s.url,
  proxy  : ss.url,
  tunnel : true
}, [
  'https connect to localhost:' + s.port,
  'http response',
  '200 http ok'
])

runTest('http over https, tunnel=false', {
  url    : s.url,
  proxy  : ss.url,
  tunnel : false
}, [
  'https proxy to http',
  'http response',
  '200 http ok'
])

runTest('http over https, tunnel=default', {
  url    : s.url,
  proxy  : ss.url
}, [
  'https proxy to http',
  'http response',
  '200 http ok'
])


// HTTPS OVER HTTP

runTest('https over http, tunnel=true', {
  url    : ss.url,
  proxy  : s.url,
  tunnel : true
}, [
  'http connect to localhost:' + ss.port,
  'https response',
  '200 https ok'
])

runTest('https over http, tunnel=false', {
  url    : ss.url,
  proxy  : s.url,
  tunnel : false
}, [
  'http proxy to https',
  'https response',
  '200 https ok'
])

runTest('https over http, tunnel=default', {
  url    : ss.url,
  proxy  : s.url
}, [
  'http connect to localhost:' + ss.port,
  'https response',
  '200 https ok'
])


// HTTPS OVER HTTPS

runTest('https over https, tunnel=true', {
  url    : ss.url,
  proxy  : ss.url,
  tunnel : true
}, [
  'https connect to localhost:' + ss.port,
  'https response',
  '200 https ok'
])

runTest('https over https, tunnel=false', {
  url    : ss.url,
  proxy  : ss.url,
  tunnel : false,
  pool   : false // must disable pooling here or Node.js hangs
}, [
  'https proxy to https',
  'https response',
  '200 https ok'
])

runTest('https over https, tunnel=default', {
  url    : ss.url,
  proxy  : ss.url
}, [
  'https connect to localhost:' + ss.port,
  'https response',
  '200 https ok'
])


// HTTP->HTTP OVER HTTP

runTest('http->http over http, tunnel=true', {
  url    : s.url + '/redirect/http',
  proxy  : s.url,
  tunnel : true
}, [
  'http connect to localhost:' + s.port,
  'http redirect to http',
  'http connect to localhost:' + s.port,
  'http response',
  '200 http ok'
])

runTest('http->http over http, tunnel=false', {
  url    : s.url + '/redirect/http',
  proxy  : s.url,
  tunnel : false
}, [
  'http proxy to http->http',
  'http redirect to http',
  'http proxy to http',
  'http response',
  '200 http ok'
])

runTest('http->http over http, tunnel=default', {
  url    : s.url + '/redirect/http',
  proxy  : s.url
}, [
  'http proxy to http->http',
  'http redirect to http',
  'http proxy to http',
  'http response',
  '200 http ok'
])


// HTTP->HTTPS OVER HTTP

runTest('http->https over http, tunnel=true', {
  url    : s.url + '/redirect/https',
  proxy  : s.url,
  tunnel : true
}, [
  'http connect to localhost:' + s.port,
  'http redirect to https',
  'http connect to localhost:' + ss.port,
  'https response',
  '200 https ok'
])

runTest('http->https over http, tunnel=false', {
  url    : s.url + '/redirect/https',
  proxy  : s.url,
  tunnel : false
}, [
  'http proxy to http->https',
  'http redirect to https',
  'http proxy to https',
  'https response',
  '200 https ok'
])

runTest('http->https over http, tunnel=default', {
  url    : s.url + '/redirect/https',
  proxy  : s.url
}, [
  'http proxy to http->https',
  'http redirect to https',
  'http connect to localhost:' + ss.port,
  'https response',
  '200 https ok'
])


// HTTPS->HTTP OVER HTTP

runTest('https->http over http, tunnel=true', {
  url    : ss.url + '/redirect/http',
  proxy  : s.url,
  tunnel : true
}, [
  'http connect to localhost:' + ss.port,
  'https redirect to http',
  'http connect to localhost:' + s.port,
  'http response',
  '200 http ok'
])

runTest('https->http over http, tunnel=false', {
  url    : ss.url + '/redirect/http',
  proxy  : s.url,
  tunnel : false
}, [
  'http proxy to https->http',
  'https redirect to http',
  'http proxy to http',
  'http response',
  '200 http ok'
])

runTest('https->http over http, tunnel=default', {
  url    : ss.url + '/redirect/http',
  proxy  : s.url
}, [
  'http connect to localhost:' + ss.port,
  'https redirect to http',
  'http proxy to http',
  'http response',
  '200 http ok'
])


// HTTPS->HTTPS OVER HTTP

runTest('https->https over http, tunnel=true', {
  url    : ss.url + '/redirect/https',
  proxy  : s.url,
  tunnel : true
}, [
  'http connect to localhost:' + ss.port,
  'https redirect to https',
  'http connect to localhost:' + ss.port,
  'https response',
  '200 https ok'
])

runTest('https->https over http, tunnel=false', {
  url    : ss.url + '/redirect/https',
  proxy  : s.url,
  tunnel : false
}, [
  'http proxy to https->https',
  'https redirect to https',
  'http proxy to https',
  'https response',
  '200 https ok'
])

runTest('https->https over http, tunnel=default', {
  url    : ss.url + '/redirect/https',
  proxy  : s.url
}, [
  'http connect to localhost:' + ss.port,
  'https redirect to https',
  'http connect to localhost:' + ss.port,
  'https response',
  '200 https ok'
])


// MUTUAL HTTPS OVER HTTP

runTest('mutual https over http, tunnel=true', {
  url        : ss2.url,
  proxy      : s.url,
  tunnel     : true,
  cert       : clientCert,
  key        : clientKey,
  passphrase : clientPassword
}, [
  'http connect to localhost:' + ss2.port,
  'https response',
  '200 https ok'
])

// XXX causes 'Error: socket hang up'
// runTest('mutual https over http, tunnel=false', {
//   url        : ss2.url,
//   proxy      : s.url,
//   tunnel     : false,
//   cert       : clientCert,
//   key        : clientKey,
//   passphrase : clientPassword
// }, [
//   'http connect to localhost:' + ss2.port,
//   'https response',
//   '200 https ok'
// ])

runTest('mutual https over http, tunnel=default', {
  url        : ss2.url,
  proxy      : s.url,
  cert       : clientCert,
  key        : clientKey,
  passphrase : clientPassword
}, [
  'http connect to localhost:' + ss2.port,
  'https response',
  '200 https ok'
])


tape('cleanup', function(t) {
  s.destroy(function() {
    ss.destroy(function() {
      ss2.destroy(function() {
        t.end()
      })
    })
  })
})