diff --git a/README.md b/README.md index eeb3f9868..82955a1dd 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ First, you need to make sure that [openssl](https://github.com/openssl/openssl) openssl req -newkey rsa:2048 -new -nodes -x509 -days 3650 -keyout key.pem -out cert.pem ``` -You will be prompted with a few questions after entering the command. Use `127.0.0.1` as value for `Common name` if you want to be able to install the certificate in your OS's root certificate store or browser so that it is trusted. +You will be prompted with a few questions after entering the command. Use `localhost` as value for `Common Name` if you want to be able to install the certificate in your OS's root certificate store or browser so that it is trusted. This generates a cert-key pair and it will be valid for 3650 days (about 10 years). @@ -126,9 +126,8 @@ This is what should be output if successful: ``` sh Starting up http-server, serving ./ through https Available on: - https:127.0.0.1:8080 - https:192.168.1.101:8080 - https:192.168.1.104:8080 + https://localhost:8080 + https://192.168.1.101:8080 Hit CTRL-C to stop the server ``` diff --git a/bin/http-server b/bin/http-server index 42ebf651f..839041dab 100755 --- a/bin/http-server +++ b/bin/http-server @@ -8,6 +8,7 @@ var colors = require('colors/safe'), portfinder = require('portfinder'), opener = require('opener'), fs = require('fs'), + x509 = require('x509.js'), argv = require('minimist')(process.argv.slice(2)); var ifaces = os.networkInterfaces(); @@ -138,23 +139,25 @@ function listen(port) { } } + var commonName; if (ssl) { options.https = { cert: argv.C || argv.cert || 'cert.pem', key: argv.K || argv.key || 'key.pem' }; try { - fs.lstatSync(options.https.cert); + var c = x509.parseCert(fs.readFileSync(options.https.cert)); + commonName = c.subject.commonName; } catch (err) { - logger.info(colors.red('Error: Could not find certificate ' + options.https.cert)); + logger.info(colors.red('Error: Could not read certificate ' + err)); process.exit(1); } try { - fs.lstatSync(options.https.key); + fs.readFileSync(options.https.key); } catch (err) { - logger.info(colors.red('Error: Could not find private key ' + options.https.key)); + logger.info(colors.red('Error: Could not read private key ' + err)); process.exit(1); } } @@ -170,19 +173,27 @@ function listen(port) { colors.yellow('\nAvailable on:') ].join('')); + var hosts = []; if (argv.a && host !== '0.0.0.0') { - logger.info((' ' + protocol + canonicalHost + ':' + colors.green(port.toString()))); + hosts.push(canonicalHost); } else { + if (ssl && commonName) { + hosts.push(commonName); + } Object.keys(ifaces).forEach(function (dev) { ifaces[dev].forEach(function (details) { if (details.family === 'IPv4') { - logger.info((' ' + protocol + details.address + ':' + colors.green(port.toString()))); + hosts.push(details.address); } }); }); } + hosts.forEach(function (host) { + logger.info((' ' + protocol + host + ':' + colors.green(port.toString()))); + }); + if (typeof proxy === 'string') { logger.info('Unhandled requests will be served from: ' + proxy); } diff --git a/package.json b/package.json index c6ecb7e1b..d2f356b25 100644 --- a/package.json +++ b/package.json @@ -90,7 +90,8 @@ "opener": "^1.5.1", "portfinder": "^1.0.25", "secure-compare": "3.0.1", - "union": "~0.5.0" + "union": "~0.5.0", + "x509.js": "^1.0.0" }, "devDependencies": { "common-style": "^3.0.0", diff --git a/test/fixtures/README.md b/test/fixtures/README.md new file mode 100644 index 000000000..164492bc2 --- /dev/null +++ b/test/fixtures/README.md @@ -0,0 +1,32 @@ +# Directory of testing fixtures + +## To create fake certificates + +```shell +# Create a fake CA cert (with key and cert in the same file) +openssl req -x509 -new \ + -subj "/CN=Fake CA" \ + -days 3650 \ + -newkey rsa:2048 -nodes \ + -keyout ca.pem -out ca.pem + +# Create CSR +openssl req -new \ + -subj "/CN=localhost" \ + -days 3650 \ + -newkey rsa:2048 -nodes \ + -keyout key.pem -out csr.pem + +# Sign the CSR to generate a certificate (with additional SubjectAltNames) +openssl x509 -req \ + -in csr.pem \ + -CA ca.pem -CAcreateserial \ + -extensions SAN \ + -extfile <(cat /etc/ssl/openssl.cnf \ + <(printf "\n[SAN]\nsubjectAltName=DNS:localhost,IP:127.0.0.1")) \ + -days 3650 -sha256 \ + -out cert.pem + +# Cleanup +rm ca.srl csr.pem +``` diff --git a/test/fixtures/ca.pem b/test/fixtures/ca.pem new file mode 100644 index 000000000..e0a6b5689 --- /dev/null +++ b/test/fixtures/ca.pem @@ -0,0 +1,45 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQDqL6v04hS17g90 +CaqBQ16qx7GqTGM/29udshsW/YCgLCq0KHcl+T4SwVZIG0xHFSuU5SHafXh3CVIv +8ZuQzaYZwZRF80If6Ti5MuPkzMyGJ7IsqBYjhRYvo+wt2WYvcA6GHbHrEn2YD0OB +2sCaFizRiEnVLb9cBfBs4VWpvUj5oMaEK8veC1Ojwb9Uq9PD/ScgNOWvMWDiMoXC +xOJDt1jx38+isQ7jmr+RL44AKOd2Y64NYx1PQm5JWoq1/dZKyB1jk8fA90TEiDdY +aVBdM4Lj3GFDjwOYgX4zirQJcB4LzxX8kgis//WHtjtGXHDaCd4S3xPM4Zzl7tzl ++k//7rgtAgMBAAECggEBANFRvu9pTH24xVNAeIiFgQ5A48qF8IhZqZjwY0pPWDLS +h3D0YlssxpDZApf83lcC0yuJCpNSZuRvDmkQGa56QibvYeqMHeSL/0l59TzC2WRo +Atfrfa5N/KCkciwhDzcDf9fcnvSwWFYb/okIz/JqM7EtkmDbPRmxrU6Esp6/M4T6 +u9fDjzYFqShKaYVd8ykSXUBN0utvBZwdV1gcS/lysnmRykbkOTSLh35HRhfHb5L6 +TggwqjuQTG5EOwpKqct3H2/J+wgoK0p5u7DsIi1UdbxzhaPk7tEfb9UBt6QpXlXr +qkhufSvyQJwSEqyjtfgbRHx2TaQxMUp8LT/COoCjefkCgYEA+4g9WybRJitu0E5Q +rntUELxHzKH4THKWdUjj1RChwy81YI+O6M+vYMGX62wF9JGhvSmQsTGdNaDk+Hpc +PY1IRljtZFuZOWVmSzPYRoTNjw+fl68ecRA1K+4OXN4k4QOcL+OvlRMH1xw0Bgey +8Hw52kI0loNEBFHAslb7i4XzCmMCgYEA7liOkgcM3tu7OdglvS4inRz71DcAsvu9 +24CenWI4A5wRDnsOOFMXqIC0w/Zzl2EBCvgoc/syY43KBlkUR7ckGs1124ZJmlQA +W7ZVFSqktpScJ4ip7t3bjChfhTJwb4vfyx1q4izmNQe5axm7XUP2J+En438hQ9Cm +OgeOEQoR8C8CgYEAuElJKs91zRFlTxkR48RYAyrvL+47jUcnFSciRai529dqtCR5 +//ip9anhNIsgkd9hMMaTTD+dfv0yxRphGne4zFG7HBxAVt0D5XVGr+P89yPrOacE +FrJZQqZXv5LCUlnixPN8YSxgQipXs1NQtwFNIav/+4aQ/tkm5YL1KXQqbSECgYB9 +kmqKxOwi/eFGOHqpkQTrgbmrtM7JfZgpbToj8PtR64eQ+YQkaKKuRCD4nX+I4bKK +9PNbJ50Elk85yPTGU9bRyC2v2rAsftUxfH6XCEB/cQxUR8w/7OMelKa+pjRWkHr3 +qCgHwWAH0Gn/4y6zlHq7yAQb936vG9539EME9yk8QQKBgQDr+eK6Lf0mokcm3LtG +n5P1a0MR8gWQhSJXMxwW1emib2MaqttAR5zsLci7mA9DyioflH+s3vLkMlKDMATT +o9OTQw+yFTVCVigFORl01dG73dd9A2/58vx6/eiYfS9IHx+ZLB0Llerl/2Wv7muP +UALOcqExJmRFaz5zzpgtCY6fVw== +-----END PRIVATE KEY----- +-----BEGIN CERTIFICATE----- +MIICoDCCAYgCCQDszIBncV4ARjANBgkqhkiG9w0BAQsFADASMRAwDgYDVQQDDAdG +YWtlIENBMB4XDTIxMDEzMTIyMjQ0N1oXDTMxMDEyOTIyMjQ0N1owEjEQMA4GA1UE +AwwHRmFrZSBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAOovq/Ti +FLXuD3QJqoFDXqrHsapMYz/b252yGxb9gKAsKrQodyX5PhLBVkgbTEcVK5TlIdp9 +eHcJUi/xm5DNphnBlEXzQh/pOLky4+TMzIYnsiyoFiOFFi+j7C3ZZi9wDoYdsesS +fZgPQ4HawJoWLNGISdUtv1wF8GzhVam9SPmgxoQry94LU6PBv1Sr08P9JyA05a8x +YOIyhcLE4kO3WPHfz6KxDuOav5EvjgAo53Zjrg1jHU9CbklairX91krIHWOTx8D3 +RMSIN1hpUF0zguPcYUOPA5iBfjOKtAlwHgvPFfySCKz/9Ye2O0ZccNoJ3hLfE8zh +nOXu3OX6T//uuC0CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAUAaeso28jVzeaN5N +ZeMB0DA2tE97AKIra9zgYXpjfFyXHQM8z9skrHvOjVAEGRi7IqrAuhvErbUoJ4r4 +KNdjVH4ZIzbnMsm3dHkpc1pKJrDwHnmkOJ38RZPJH0V2WNnoqaJXEME0zuoq6LhO +L/dpxwXY9XWa5UZ6X/Xedxq1Mm8VBsLP3Viso617yhVbZG5YHQvcxI6DqfIZZhFD +SxcpZuzpqoPIfXvXJ0pu8nMDnbdEYsEKFkrA0iwAkDy/h2GLzxZVEOGew8Bltyo8 +1eENM6kHAfZbuewiJLv0EvjpGwsA7cYm/5+3uUwIqsQSZxhyZn8jKFOqLwpNxbsr +HspwXw== +-----END CERTIFICATE----- diff --git a/test/fixtures/cert.pem b/test/fixtures/cert.pem new file mode 100644 index 000000000..4b90574e0 --- /dev/null +++ b/test/fixtures/cert.pem @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICxzCCAa+gAwIBAgIJAKXh5y4t6LPVMA0GCSqGSIb3DQEBCwUAMBIxEDAOBgNV +BAMMB0Zha2UgQ0EwHhcNMjEwMTMxMjIyNDQ3WhcNMzEwMTI5MjIyNDQ3WjAUMRIw +EAYDVQQDDAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB +AQDC6GvrMFyBkcdDzoiKTj4MnxP+BwE3R68d7jU38r/wChsuvQ6V3HtketAD6Ocl +OUTj2i+DfXLzItOmUmL3PmynwtyKxlehwg7iDDFN/mvRThBmdclG9xvhCY+PaUzn +JJvV+CCGiYwPifNhnSRUsJqMQZ1yNkMrY+nuoO5SlhKTwISBNYACDyA6W8Qvx1rw +m5EHNdZ67Vetp7T9o6bhTHoqYSRtNRsAgu4xe0H40AlDR7xajmiaqX5u6FV4Rb7f +bkJPRjPsXMRIdN2tKS3yToL0SSoa0kGVmrSXS2Yo/mG6n3jb0/lqSqzRvQ3C1h0U +9VEpahfxi3ZDF3GvgWz3OB/jAgMBAAGjHjAcMBoGA1UdEQQTMBGCCWxvY2FsaG9z +dIcEfwAAATANBgkqhkiG9w0BAQsFAAOCAQEAgcBDvaPmpg9gJusAUo8edIAuFpQw +fp0bIJ0KY4fgCihiclAlhE7Jn0S/6Kr7XfK7ZTqxMArXygvDfOWHv4fWDlIQR1fv +pxMdQq5ef/TWGBlvSONC5Kdivmby7YSkxaCt/xcvKL5dculkpMcC24IeIamVSOFf +M/3++ma0w9K/xUSV0KwKCpLv/N4tRqlCAfv9Vk+DtwKKaloqisjwjFlxwWmjOChx +CDEfg8ys5zV6gVGcjn5Oa/SaY6FboD8t9H0janEkoFD57xTKHWBIfcbTufjeMjGT +ukU196yctwoQaDQ+fHk/brDN6h5w3HOKVj5AiMOuu9sKT5jJGbgU9NU9Ow== +-----END CERTIFICATE----- diff --git a/test/fixtures/key.pem b/test/fixtures/key.pem new file mode 100644 index 000000000..024a0b1ab --- /dev/null +++ b/test/fixtures/key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDC6GvrMFyBkcdD +zoiKTj4MnxP+BwE3R68d7jU38r/wChsuvQ6V3HtketAD6OclOUTj2i+DfXLzItOm +UmL3PmynwtyKxlehwg7iDDFN/mvRThBmdclG9xvhCY+PaUznJJvV+CCGiYwPifNh +nSRUsJqMQZ1yNkMrY+nuoO5SlhKTwISBNYACDyA6W8Qvx1rwm5EHNdZ67Vetp7T9 +o6bhTHoqYSRtNRsAgu4xe0H40AlDR7xajmiaqX5u6FV4Rb7fbkJPRjPsXMRIdN2t +KS3yToL0SSoa0kGVmrSXS2Yo/mG6n3jb0/lqSqzRvQ3C1h0U9VEpahfxi3ZDF3Gv +gWz3OB/jAgMBAAECggEAMu+YnGsUEcxuHdtQtYxDDPtZty5PdAnoytKg19E5tdp+ +RhWkRSfMm3K4//ySw5iW11ECz8MuEjpMw4+OS3zl2mXDSwUQi7ZyO0Lic7aEqLtU +7+HiSwhzIblk6h6juVhI3X8tyNsTXlA36Y+ume9ZREQ1iE6D+UfwL6ug/LY5fqEA ++NH9ofg2vT7qd42mhZjiOPvxMrKdRHn2X9gG5wb99wj3YBODjuY3EXiRLSPLSxyE +jQtwK5vVUEY05Fpe21a1HS3wixEcqQBp/KEfysVj83Aj+I49tfB/ORg++ks0cqv1 +jJ/8wviUZtg2RWM9xTXV8GdcsszipTpwuj8eiGXEOQKBgQD1lqzp033+S/3YIS0/ +WbF+JtFdXTCV5Kq4RlI75uiczUUVdRltV+yDfzarGgUTT0CpSZzEUHr7Ugd95AMR +G9KLGCjgI8hktHT0LbAjqcfOP+/azcQLaOgT5iVuT7ZlpqhlCacVUiY9G3gWFq0B +aYktbjxgjIDGFWx8oLayjN6pnwKBgQDLK7f6MaX5Jf2MDzK3lt0K4bw5pBXQCxiY +yEMaMBjJBS0uiXS5WyKAhh6Vf+D4sKI7JWRMVwlD1wHq7WbCERIqR7wo8eI8uDr/ +ltVkcjYKS2N3vNPDpZKEzoMxSFnOONGaAgNggAjxZvNasD+Z6BV5RSqPni0PXDZD +9B0PeNQrPQKBgEqYI5k6NfDBoC6/lQDC+5h3rewP3CwLMpeaNGwhbNIDv1IPKVP+ ++sXOJArAcn40+kzxIP63+0LO3ZutYAkYTFEXW4MJG1sLPOLV5cRPU3MgFHh/O2bD +zIoOw5vH9nzVrBxUXD2roBW7fDQpWw8swQ/dhdVFl++SnksUfamqBA+9AoGAL6bK +WMEKR3xUkmQCJjMZFvNI3VAR2aCwnSzjKCI9vfAb371Xhh3M4s4SIEhE8K8k7bBg +bNNBFgs4pOwXXM76LrZyeDv7LviaxdWPqSZsbE+wPaYpGMsdqU5yUL3Cam7DIlb0 +ic6dyli2HQAXeraHStEhIVwc/2xGQfvgUP+q65UCgYADYixLX4cfGGwgZyLP8ppt +rE34XVOIOYDcyfqEg/Z/1IcihGKb4uJy6QYC0HuirRss4uDllsNacJflNUsQrxoR +1UdAXHZw5Zz7BXCIo+GEAAF8Ejzyjz2+Q05wZ8RP4S9FReh7EUD/q2KuPGpBLlwI +TzwhiVaIjNPgkC+hrJZpZw== +-----END PRIVATE KEY----- diff --git a/test/http-server-test.js b/test/http-server-test.js index 6d5fce462..53cbff2f1 100644 --- a/test/http-server-test.js +++ b/test/http-server-test.js @@ -10,6 +10,53 @@ process.on('uncaughtException', console.error); var root = path.join(__dirname, 'fixtures', 'root'); +// respondsWith makes a request to the URL with the optional opts, and asserts +// the returned response is code with the expected body. +function respondsWith(code, url, opts) { + var context = { + topic: function (server) { + opts = opts || {}; + request(url, opts, this.callback); + } + }; + context['status code should be ' + code] = function (err, res) { + assert.isNull(err); + assert.equal(res.statusCode, code); + }; + return context; +} + +// respondsWithMessage makes a request to the URL with the optional opts, and asserts +// the returned response is `code` and the response body is `message`. +function respondsWithMessage(code, message, url, opts) { + var context = respondsWith(code, url, opts); + context['and file content should be "' + message + '"'] = function (err, res, body) { + assert.isNull(err); + assert.equal(body, message); + }; + return context; +} + +// respondsWithFile makes a request to the URL with the optional opts, and asserts +// the returned status is `code` and the response body matches the expected filename +// contents. +function respondsWithFile(code, filename, url, opts) { + var context = respondsWith(code, url, opts); + context['and file content'] = { + topic: function (res, body) { + var self = this; + fs.readFile(path.join(root, filename), 'utf8', function (err, data) { + self.callback(err, data, body); + }); + }, + 'should match content of served file': function (err, data, body) { + assert.isNull(err); + assert.equal(data.trim(), body.trim()); + } + }; + return context; +} + vows.describe('http-server').addBatch({ 'When http-server is listening on 8080,\n': { topic: function () { @@ -23,35 +70,14 @@ vows.describe('http-server').addBatch({ }); server.listen(8080); - this.callback(null, server); - }, - 'it should serve files from root directory': { - topic: function () { - request('http://127.0.0.1:8080/file', this.callback); - }, - 'status code should be 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } - } + return server; }, - 'and a non-existent file is requested...': { - topic: function () { - request('http://127.0.0.1:8080/404', this.callback); - }, - 'status code should be 404': function (res) { - assert.equal(res.statusCode, 404); - } + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, + 'it should serve files from root directory': respondsWithFile(200, 'file', 'http://127.0.0.1:8080/file'), + 'and a non-existent file is requested...': respondsWith(404, 'http://127.0.0.1:8080/404'), 'requesting /': { topic: function () { request('http://127.0.0.1:8080/', this.callback); @@ -66,7 +92,7 @@ vows.describe('http-server').addBatch({ topic: function () { request('http://127.0.0.1:8080/robots.txt', this.callback); }, - 'should respond with status code 200 to /robots.txt': function (res) { + 'should respond with status code 200 to /robots.txt': function (err, res) { assert.equal(res.statusCode, 200); } }, @@ -86,46 +112,15 @@ vows.describe('http-server').addBatch({ root: path.join(__dirname, 'fixtures') }); proxyServer.listen(8081); - this.callback(null, proxyServer); + return proxyServer; }, - '\nit should serve files from the proxy\'s root': { - topic: function () { - request('http://127.0.0.1:8081/root/file', this.callback); - }, - 'status code should be the endpoint code 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of the served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } - } - }, - '\nit should fallback to the proxied server': { - topic: function () { - request('http://127.0.0.1:8081/file', this.callback); - }, - 'status code should be the endpoint code 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of the proxied served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } - } + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, + '\nit should serve files from the proxy\'s root': respondsWithFile(200, 'file', 'http://127.0.0.1:8081/root/file'), + '\nit should fallback to the proxied server': respondsWithFile(200, 'file', 'http://127.0.0.1:8081/file'), + teardown: function (proxyServer) { proxyServer.close(); } @@ -142,7 +137,11 @@ vows.describe('http-server').addBatch({ corsHeaders: 'X-Test' }); server.listen(8082); - this.callback(null, server); + return server; + }, + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, 'and the server is given an OPTIONS request': { topic: function () { @@ -175,7 +174,11 @@ vows.describe('http-server').addBatch({ gzip: true }); server.listen(8084); - this.callback(null, server); + return server; + }, + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, 'and a request accepting only gzip is made': { topic: function () { @@ -237,123 +240,37 @@ vows.describe('http-server').addBatch({ }); server.listen(8083); - this.callback(null, server); + return server; }, - 'and the user requests an existent file with no auth details': { - topic: function () { - request('http://127.0.0.1:8083/file', this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } - } + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, - 'and the user requests an existent file with incorrect username': { - topic: function () { - request('http://127.0.0.1:8083/file', { - auth: { - user: 'wrong_username', - pass: 'good_password' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + 'and the user requests an existent file with no auth details': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file'), + 'and the user requests an existent file with incorrect username': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { + auth: { + user: 'wrong_username', + pass: 'good_password' } - }, - 'and the user requests an existent file with incorrect password': { - topic: function () { - request('http://127.0.0.1:8083/file', { - auth: { - user: 'good_username', - pass: 'wrong_password' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + }), + 'and the user requests an existent file with incorrect password': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { + auth: { + user: 'good_username', + pass: 'wrong_password' } - }, - 'and the user requests a non-existent file with incorrect password': { - topic: function () { - request('http://127.0.0.1:8083/404', { - auth: { - user: 'good_username', - pass: 'wrong_password' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + }), + 'and the user requests a non-existent file with incorrect password': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/404', { + auth: { + user: 'good_username', + pass: 'wrong_password' } - }, - 'and the user requests an existent file with correct auth details': { - topic: function () { - request('http://127.0.0.1:8083/file', { - auth: { - user: 'good_username', - pass: 'good_password' - } - }, this.callback); - }, - 'status code should be 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } + }), + 'and the user requests an existent file with correct auth details': respondsWithFile(200, 'file', 'http://127.0.0.1:8083/file', { + auth: { + user: 'good_username', + pass: 'good_password' } - }, + }), teardown: function (server) { server.close(); } @@ -365,7 +282,11 @@ vows.describe('http-server').addBatch({ ext: true }); server.listen(8085); - this.callback(null, server); + return server; + }, + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, 'and a file with no extension is requested with default options,': { topic: function () { @@ -394,125 +315,75 @@ vows.describe('http-server').addBatch({ }); server.listen(8086); - this.callback(null, server); + return server; }, - 'and the user requests an existent file with no auth details': { - topic: function () { - request('http://127.0.0.1:8086/file', this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } - } + 'it should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); }, - 'and the user requests an existent file with incorrect username': { - topic: function () { - request('http://127.0.0.1:8086/file', { - auth: { - user: 'wrong_username', - pass: '123456' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + 'and the user requests an existent file with no auth details': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8086/file'), + 'and the user requests an existent file with incorrect username': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { + auth: { + user: 'wrong_username', + pass: '123456' } - }, - 'and the user requests an existent file with incorrect password': { - topic: function () { - request('http://127.0.0.1:8086/file', { - auth: { - user: 'good_username', - pass: '654321' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + }), + 'and the user requests an existent file with incorrect password': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/file', { + auth: { + user: 'good_username', + pass: '654321' } - }, - 'and the user requests a non-existent file with incorrect password': { - topic: function () { - request('http://127.0.0.1:8086/404', { - auth: { - user: 'good_username', - pass: '654321' - } - }, this.callback); - }, - 'status code should be 401': function (res) { - assert.equal(res.statusCode, 401); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should be a forbidden message': function (err, file, body) { - assert.equal(body, 'Access denied'); - } + }), + 'and the user requests a non-existent file with incorrect password': respondsWithMessage(401, 'Access denied', 'http://127.0.0.1:8083/404', { + auth: { + user: 'good_username', + pass: '654321' } + }), + 'and the user requests an existent file with correct auth details': respondsWithFile(200, 'file', 'http://127.0.0.1:8086/file', { + auth: { + user: 'good_username', + pass: '123456' + } + }), + teardown: function (server) { + server.close(); + } + }, + 'When SSL is enabled': { + topic: function () { + var server = httpServer.createServer({ + https: { + cert: path.join(__dirname, 'fixtures', 'cert.pem'), + key: path.join(__dirname, 'fixtures', 'key.pem') + } + }); + server.listen(8087); + return server; }, - 'and the user requests an existent file with correct auth details': { - topic: function () { - request('http://127.0.0.1:8086/file', { - auth: { - user: 'good_username', - pass: '123456' - } + 'the server should start without errors': function (err, server) { + assert.isNull(err); + assert.isTrue(server.server.listening); + }, + 'and it should serve files': { + topic: function (server) { + var caFile = path.join(__dirname, 'fixtures', 'ca.pem'); + request('https://127.0.0.1:8087/', { + ca: fs.readFileSync(caFile) }, this.callback); }, - 'status code should be 200': function (res) { - assert.equal(res.statusCode, 200); - }, - 'and file content': { - topic: function (res, body) { - var self = this; - fs.readFile(path.join(root, 'file'), 'utf8', function (err, data) { - self.callback(err, data, body); - }); - }, - 'should match content of served file': function (err, file, body) { - assert.equal(body.trim(), file.trim()); - } + 'over SSL': function (err, res) { + assert.isNull(err); + + var peerCert = res.socket.getPeerCertificate(); + assert.isNotNull(peerCert); + assert.equal(peerCert.subject.CN, 'localhost'); } }, teardown: function (server) { - server.close(); + if (server) { + server.close(); + } } } }).export(module);