/test/res.sendFile.js

http://github.com/visionmedia/express · JavaScript · 826 lines · 691 code · 135 blank · 0 comment · 7 complexity · 4f961aca13d23dc020b80ef45ca92860 MD5 · raw file

  1. var after = require('after');
  2. var Buffer = require('safe-buffer').Buffer
  3. var express = require('../')
  4. , request = require('supertest')
  5. , assert = require('assert');
  6. var onFinished = require('on-finished');
  7. var path = require('path');
  8. var should = require('should');
  9. var fixtures = path.join(__dirname, 'fixtures');
  10. var utils = require('./support/utils');
  11. describe('res', function(){
  12. describe('.sendFile(path)', function () {
  13. it('should error missing path', function (done) {
  14. var app = createApp();
  15. request(app)
  16. .get('/')
  17. .expect(500, /path.*required/, done);
  18. });
  19. it('should error for non-string path', function (done) {
  20. var app = createApp(42)
  21. request(app)
  22. .get('/')
  23. .expect(500, /TypeError: path must be a string to res.sendFile/, done)
  24. })
  25. it('should transfer a file', function (done) {
  26. var app = createApp(path.resolve(fixtures, 'name.txt'));
  27. request(app)
  28. .get('/')
  29. .expect(200, 'tobi', done);
  30. });
  31. it('should transfer a file with special characters in string', function (done) {
  32. var app = createApp(path.resolve(fixtures, '% of dogs.txt'));
  33. request(app)
  34. .get('/')
  35. .expect(200, '20%', done);
  36. });
  37. it('should include ETag', function (done) {
  38. var app = createApp(path.resolve(fixtures, 'name.txt'));
  39. request(app)
  40. .get('/')
  41. .expect('ETag', /^(?:W\/)?"[^"]+"$/)
  42. .expect(200, 'tobi', done);
  43. });
  44. it('should 304 when ETag matches', function (done) {
  45. var app = createApp(path.resolve(fixtures, 'name.txt'));
  46. request(app)
  47. .get('/')
  48. .expect('ETag', /^(?:W\/)?"[^"]+"$/)
  49. .expect(200, 'tobi', function (err, res) {
  50. if (err) return done(err);
  51. var etag = res.headers.etag;
  52. request(app)
  53. .get('/')
  54. .set('If-None-Match', etag)
  55. .expect(304, done);
  56. });
  57. });
  58. it('should 404 for directory', function (done) {
  59. var app = createApp(path.resolve(fixtures, 'blog'));
  60. request(app)
  61. .get('/')
  62. .expect(404, done);
  63. });
  64. it('should 404 when not found', function (done) {
  65. var app = createApp(path.resolve(fixtures, 'does-no-exist'));
  66. app.use(function (req, res) {
  67. res.statusCode = 200;
  68. res.send('no!');
  69. });
  70. request(app)
  71. .get('/')
  72. .expect(404, done);
  73. });
  74. it('should not override manual content-types', function (done) {
  75. var app = express();
  76. app.use(function (req, res) {
  77. res.contentType('application/x-bogus');
  78. res.sendFile(path.resolve(fixtures, 'name.txt'));
  79. });
  80. request(app)
  81. .get('/')
  82. .expect('Content-Type', 'application/x-bogus')
  83. .end(done);
  84. })
  85. it('should not error if the client aborts', function (done) {
  86. var app = express();
  87. var cb = after(2, done)
  88. var error = null
  89. app.use(function (req, res) {
  90. setImmediate(function () {
  91. res.sendFile(path.resolve(fixtures, 'name.txt'));
  92. server.close(cb)
  93. setTimeout(function () {
  94. cb(error)
  95. }, 10)
  96. })
  97. test.abort();
  98. });
  99. app.use(function (err, req, res, next) {
  100. error = err
  101. next(err)
  102. });
  103. var server = app.listen()
  104. var test = request(server).get('/')
  105. test.end()
  106. })
  107. describe('with "cacheControl" option', function () {
  108. it('should enable cacheControl by default', function (done) {
  109. var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'))
  110. request(app)
  111. .get('/')
  112. .expect('Cache-Control', 'public, max-age=0')
  113. .expect(200, done)
  114. })
  115. it('should accept cacheControl option', function (done) {
  116. var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), { cacheControl: false })
  117. request(app)
  118. .get('/')
  119. .expect(utils.shouldNotHaveHeader('Cache-Control'))
  120. .expect(200, done)
  121. })
  122. })
  123. describe('with "dotfiles" option', function () {
  124. it('should not serve dotfiles by default', function (done) {
  125. var app = createApp(path.resolve(__dirname, 'fixtures/.name'));
  126. request(app)
  127. .get('/')
  128. .expect(404, done);
  129. });
  130. it('should accept dotfiles option', function(done){
  131. var app = createApp(path.resolve(__dirname, 'fixtures/.name'), { dotfiles: 'allow' });
  132. request(app)
  133. .get('/')
  134. .expect(200)
  135. .expect(shouldHaveBody(Buffer.from('tobi')))
  136. .end(done)
  137. });
  138. });
  139. describe('with "headers" option', function () {
  140. it('should accept headers option', function (done) {
  141. var headers = {
  142. 'x-success': 'sent',
  143. 'x-other': 'done'
  144. };
  145. var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), { headers: headers });
  146. request(app)
  147. .get('/')
  148. .expect('x-success', 'sent')
  149. .expect('x-other', 'done')
  150. .expect(200, done);
  151. });
  152. it('should ignore headers option on 404', function (done) {
  153. var headers = { 'x-success': 'sent' };
  154. var app = createApp(path.resolve(__dirname, 'fixtures/does-not-exist'), { headers: headers });
  155. request(app)
  156. .get('/')
  157. .expect(utils.shouldNotHaveHeader('X-Success'))
  158. .expect(404, done);
  159. });
  160. });
  161. describe('with "immutable" option', function () {
  162. it('should add immutable cache-control directive', function (done) {
  163. var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), {
  164. immutable: true,
  165. maxAge: '4h'
  166. })
  167. request(app)
  168. .get('/')
  169. .expect('Cache-Control', 'public, max-age=14400, immutable')
  170. .expect(200, done)
  171. })
  172. })
  173. describe('with "maxAge" option', function () {
  174. it('should set cache-control max-age from number', function (done) {
  175. var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), {
  176. maxAge: 14400000
  177. })
  178. request(app)
  179. .get('/')
  180. .expect('Cache-Control', 'public, max-age=14400')
  181. .expect(200, done)
  182. })
  183. it('should set cache-control max-age from string', function (done) {
  184. var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), {
  185. maxAge: '4h'
  186. })
  187. request(app)
  188. .get('/')
  189. .expect('Cache-Control', 'public, max-age=14400')
  190. .expect(200, done)
  191. })
  192. })
  193. describe('with "root" option', function () {
  194. it('should not transfer relative with without', function (done) {
  195. var app = createApp('test/fixtures/name.txt');
  196. request(app)
  197. .get('/')
  198. .expect(500, /must be absolute/, done);
  199. })
  200. it('should serve relative to "root"', function (done) {
  201. var app = createApp('name.txt', {root: fixtures});
  202. request(app)
  203. .get('/')
  204. .expect(200, 'tobi', done);
  205. })
  206. it('should disallow requesting out of "root"', function (done) {
  207. var app = createApp('foo/../../user.html', {root: fixtures});
  208. request(app)
  209. .get('/')
  210. .expect(403, done);
  211. })
  212. })
  213. })
  214. describe('.sendFile(path, fn)', function () {
  215. it('should invoke the callback when complete', function (done) {
  216. var cb = after(2, done);
  217. var app = createApp(path.resolve(fixtures, 'name.txt'), cb);
  218. request(app)
  219. .get('/')
  220. .expect(200, cb);
  221. })
  222. it('should invoke the callback when client aborts', function (done) {
  223. var cb = after(1, done);
  224. var app = express();
  225. app.use(function (req, res) {
  226. setImmediate(function () {
  227. res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
  228. should(err).be.ok()
  229. err.code.should.equal('ECONNABORTED');
  230. server.close(cb)
  231. });
  232. });
  233. test.abort();
  234. });
  235. var server = app.listen()
  236. var test = request(server).get('/')
  237. test.expect(200, cb);
  238. })
  239. it('should invoke the callback when client already aborted', function (done) {
  240. var cb = after(1, done);
  241. var app = express();
  242. app.use(function (req, res) {
  243. onFinished(res, function () {
  244. res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
  245. should(err).be.ok()
  246. err.code.should.equal('ECONNABORTED');
  247. server.close(cb)
  248. });
  249. });
  250. test.abort();
  251. });
  252. var server = app.listen()
  253. var test = request(server).get('/')
  254. test.expect(200, cb);
  255. })
  256. it('should invoke the callback without error when HEAD', function (done) {
  257. var app = express();
  258. var cb = after(2, done);
  259. app.use(function (req, res) {
  260. res.sendFile(path.resolve(fixtures, 'name.txt'), cb);
  261. });
  262. request(app)
  263. .head('/')
  264. .expect(200, cb);
  265. });
  266. it('should invoke the callback without error when 304', function (done) {
  267. var app = express();
  268. var cb = after(3, done);
  269. app.use(function (req, res) {
  270. res.sendFile(path.resolve(fixtures, 'name.txt'), cb);
  271. });
  272. request(app)
  273. .get('/')
  274. .expect('ETag', /^(?:W\/)?"[^"]+"$/)
  275. .expect(200, 'tobi', function (err, res) {
  276. if (err) return cb(err);
  277. var etag = res.headers.etag;
  278. request(app)
  279. .get('/')
  280. .set('If-None-Match', etag)
  281. .expect(304, cb);
  282. });
  283. });
  284. it('should invoke the callback on 404', function(done){
  285. var app = express();
  286. app.use(function (req, res) {
  287. res.sendFile(path.resolve(fixtures, 'does-not-exist'), function (err) {
  288. should(err).be.ok()
  289. err.status.should.equal(404);
  290. res.send('got it');
  291. });
  292. });
  293. request(app)
  294. .get('/')
  295. .expect(200, 'got it', done);
  296. })
  297. })
  298. describe('.sendFile(path, options)', function () {
  299. it('should pass options to send module', function (done) {
  300. request(createApp(path.resolve(fixtures, 'name.txt'), { start: 0, end: 1 }))
  301. .get('/')
  302. .expect(200, 'to', done)
  303. })
  304. })
  305. describe('.sendfile(path, fn)', function(){
  306. it('should invoke the callback when complete', function(done){
  307. var app = express();
  308. var cb = after(2, done);
  309. app.use(function(req, res){
  310. res.sendfile('test/fixtures/user.html', cb)
  311. });
  312. request(app)
  313. .get('/')
  314. .expect(200, cb);
  315. })
  316. it('should utilize the same options as express.static()', function(done){
  317. var app = express();
  318. app.use(function(req, res){
  319. res.sendfile('test/fixtures/user.html', { maxAge: 60000 });
  320. });
  321. request(app)
  322. .get('/')
  323. .expect('Cache-Control', 'public, max-age=60')
  324. .end(done);
  325. })
  326. it('should invoke the callback when client aborts', function (done) {
  327. var cb = after(1, done);
  328. var app = express();
  329. app.use(function (req, res) {
  330. setImmediate(function () {
  331. res.sendfile('test/fixtures/name.txt', function (err) {
  332. should(err).be.ok()
  333. err.code.should.equal('ECONNABORTED');
  334. server.close(cb)
  335. });
  336. });
  337. test.abort();
  338. });
  339. var server = app.listen()
  340. var test = request(server).get('/')
  341. test.expect(200, cb);
  342. })
  343. it('should invoke the callback when client already aborted', function (done) {
  344. var cb = after(1, done);
  345. var app = express();
  346. app.use(function (req, res) {
  347. onFinished(res, function () {
  348. res.sendfile('test/fixtures/name.txt', function (err) {
  349. should(err).be.ok()
  350. err.code.should.equal('ECONNABORTED');
  351. server.close(cb)
  352. });
  353. });
  354. test.abort();
  355. });
  356. var server = app.listen()
  357. var test = request(server).get('/')
  358. test.expect(200, cb);
  359. })
  360. it('should invoke the callback without error when HEAD', function (done) {
  361. var app = express();
  362. var cb = after(2, done);
  363. app.use(function (req, res) {
  364. res.sendfile('test/fixtures/name.txt', cb);
  365. });
  366. request(app)
  367. .head('/')
  368. .expect(200, cb);
  369. });
  370. it('should invoke the callback without error when 304', function (done) {
  371. var app = express();
  372. var cb = after(3, done);
  373. app.use(function (req, res) {
  374. res.sendfile('test/fixtures/name.txt', cb);
  375. });
  376. request(app)
  377. .get('/')
  378. .expect('ETag', /^(?:W\/)?"[^"]+"$/)
  379. .expect(200, 'tobi', function (err, res) {
  380. if (err) return cb(err);
  381. var etag = res.headers.etag;
  382. request(app)
  383. .get('/')
  384. .set('If-None-Match', etag)
  385. .expect(304, cb);
  386. });
  387. });
  388. it('should invoke the callback on 404', function(done){
  389. var app = express();
  390. var calls = 0;
  391. app.use(function(req, res){
  392. res.sendfile('test/fixtures/nope.html', function(err){
  393. assert.equal(calls++, 0);
  394. assert(!res.headersSent);
  395. res.send(err.message);
  396. });
  397. });
  398. request(app)
  399. .get('/')
  400. .expect(200, /^ENOENT.*?, stat/, done);
  401. })
  402. it('should not override manual content-types', function(done){
  403. var app = express();
  404. app.use(function(req, res){
  405. res.contentType('txt');
  406. res.sendfile('test/fixtures/user.html');
  407. });
  408. request(app)
  409. .get('/')
  410. .expect('Content-Type', 'text/plain; charset=utf-8')
  411. .end(done);
  412. })
  413. it('should invoke the callback on 403', function(done){
  414. var app = express()
  415. app.use(function(req, res){
  416. res.sendfile('test/fixtures/foo/../user.html', function(err){
  417. assert(!res.headersSent);
  418. res.send(err.message);
  419. });
  420. });
  421. request(app)
  422. .get('/')
  423. .expect('Forbidden')
  424. .expect(200, done);
  425. })
  426. it('should invoke the callback on socket error', function(done){
  427. var app = express()
  428. app.use(function(req, res){
  429. res.sendfile('test/fixtures/user.html', function(err){
  430. assert(!res.headersSent);
  431. req.socket.listeners('error').should.have.length(1); // node's original handler
  432. done();
  433. });
  434. req.socket.emit('error', new Error('broken!'));
  435. });
  436. request(app)
  437. .get('/')
  438. .end(function(){});
  439. })
  440. })
  441. describe('.sendfile(path)', function(){
  442. it('should not serve dotfiles', function(done){
  443. var app = express();
  444. app.use(function(req, res){
  445. res.sendfile('test/fixtures/.name');
  446. });
  447. request(app)
  448. .get('/')
  449. .expect(404, done);
  450. })
  451. it('should accept dotfiles option', function(done){
  452. var app = express();
  453. app.use(function(req, res){
  454. res.sendfile('test/fixtures/.name', { dotfiles: 'allow' });
  455. });
  456. request(app)
  457. .get('/')
  458. .expect(200)
  459. .expect(shouldHaveBody(Buffer.from('tobi')))
  460. .end(done)
  461. })
  462. it('should accept headers option', function(done){
  463. var app = express();
  464. var headers = {
  465. 'x-success': 'sent',
  466. 'x-other': 'done'
  467. };
  468. app.use(function(req, res){
  469. res.sendfile('test/fixtures/user.html', { headers: headers });
  470. });
  471. request(app)
  472. .get('/')
  473. .expect('x-success', 'sent')
  474. .expect('x-other', 'done')
  475. .expect(200, done);
  476. })
  477. it('should ignore headers option on 404', function(done){
  478. var app = express();
  479. var headers = { 'x-success': 'sent' };
  480. app.use(function(req, res){
  481. res.sendfile('test/fixtures/user.nothing', { headers: headers });
  482. });
  483. request(app)
  484. .get('/')
  485. .expect(utils.shouldNotHaveHeader('X-Success'))
  486. .expect(404, done);
  487. })
  488. it('should transfer a file', function (done) {
  489. var app = express();
  490. app.use(function (req, res) {
  491. res.sendfile('test/fixtures/name.txt');
  492. });
  493. request(app)
  494. .get('/')
  495. .expect(200, 'tobi', done);
  496. });
  497. it('should transfer a directory index file', function (done) {
  498. var app = express();
  499. app.use(function (req, res) {
  500. res.sendfile('test/fixtures/blog/');
  501. });
  502. request(app)
  503. .get('/')
  504. .expect(200, '<b>index</b>', done);
  505. });
  506. it('should 404 for directory without trailing slash', function (done) {
  507. var app = express();
  508. app.use(function (req, res) {
  509. res.sendfile('test/fixtures/blog');
  510. });
  511. request(app)
  512. .get('/')
  513. .expect(404, done);
  514. });
  515. it('should transfer a file with urlencoded name', function (done) {
  516. var app = express();
  517. app.use(function (req, res) {
  518. res.sendfile('test/fixtures/%25%20of%20dogs.txt');
  519. });
  520. request(app)
  521. .get('/')
  522. .expect(200, '20%', done);
  523. });
  524. it('should not error if the client aborts', function (done) {
  525. var app = express();
  526. var cb = after(2, done)
  527. var error = null
  528. app.use(function (req, res) {
  529. setImmediate(function () {
  530. res.sendfile(path.resolve(fixtures, 'name.txt'));
  531. server.close(cb)
  532. setTimeout(function () {
  533. cb(error)
  534. }, 10)
  535. });
  536. test.abort();
  537. });
  538. app.use(function (err, req, res, next) {
  539. error = err
  540. next(err)
  541. });
  542. var server = app.listen()
  543. var test = request(server).get('/')
  544. test.end()
  545. })
  546. describe('with an absolute path', function(){
  547. it('should transfer the file', function(done){
  548. var app = express();
  549. app.use(function(req, res){
  550. res.sendfile(path.join(__dirname, '/fixtures/user.html'))
  551. });
  552. request(app)
  553. .get('/')
  554. .expect('Content-Type', 'text/html; charset=UTF-8')
  555. .expect(200, '<p>{{user.name}}</p>', done);
  556. })
  557. })
  558. describe('with a relative path', function(){
  559. it('should transfer the file', function(done){
  560. var app = express();
  561. app.use(function(req, res){
  562. res.sendfile('test/fixtures/user.html');
  563. });
  564. request(app)
  565. .get('/')
  566. .expect('Content-Type', 'text/html; charset=UTF-8')
  567. .expect(200, '<p>{{user.name}}</p>', done);
  568. })
  569. it('should serve relative to "root"', function(done){
  570. var app = express();
  571. app.use(function(req, res){
  572. res.sendfile('user.html', { root: 'test/fixtures/' });
  573. });
  574. request(app)
  575. .get('/')
  576. .expect('Content-Type', 'text/html; charset=UTF-8')
  577. .expect(200, '<p>{{user.name}}</p>', done);
  578. })
  579. it('should consider ../ malicious when "root" is not set', function(done){
  580. var app = express();
  581. app.use(function(req, res){
  582. res.sendfile('test/fixtures/foo/../user.html');
  583. });
  584. request(app)
  585. .get('/')
  586. .expect(403, done);
  587. })
  588. it('should allow ../ when "root" is set', function(done){
  589. var app = express();
  590. app.use(function(req, res){
  591. res.sendfile('foo/../user.html', { root: 'test/fixtures' });
  592. });
  593. request(app)
  594. .get('/')
  595. .expect(200, done);
  596. })
  597. it('should disallow requesting out of "root"', function(done){
  598. var app = express();
  599. app.use(function(req, res){
  600. res.sendfile('foo/../../user.html', { root: 'test/fixtures' });
  601. });
  602. request(app)
  603. .get('/')
  604. .expect(403, done);
  605. })
  606. it('should next(404) when not found', function(done){
  607. var app = express()
  608. , calls = 0;
  609. app.use(function(req, res){
  610. res.sendfile('user.html');
  611. });
  612. app.use(function(req, res){
  613. assert(0, 'this should not be called');
  614. });
  615. app.use(function(err, req, res, next){
  616. ++calls;
  617. next(err);
  618. });
  619. request(app)
  620. .get('/')
  621. .end(function(err, res){
  622. res.statusCode.should.equal(404);
  623. calls.should.equal(1);
  624. done();
  625. });
  626. })
  627. describe('with non-GET', function(){
  628. it('should still serve', function(done){
  629. var app = express()
  630. app.use(function(req, res){
  631. res.sendfile(path.join(__dirname, '/fixtures/name.txt'))
  632. });
  633. request(app)
  634. .get('/')
  635. .expect('tobi', done);
  636. })
  637. })
  638. })
  639. })
  640. })
  641. describe('.sendfile(path, options)', function () {
  642. it('should pass options to send module', function (done) {
  643. var app = express()
  644. app.use(function (req, res) {
  645. res.sendfile(path.resolve(fixtures, 'name.txt'), { start: 0, end: 1 })
  646. })
  647. request(app)
  648. .get('/')
  649. .expect(200, 'to', done)
  650. })
  651. })
  652. function createApp(path, options, fn) {
  653. var app = express();
  654. app.use(function (req, res) {
  655. res.sendFile(path, options, fn);
  656. });
  657. return app;
  658. }
  659. function shouldHaveBody (buf) {
  660. return function (res) {
  661. var body = !Buffer.isBuffer(res.body)
  662. ? Buffer.from(res.text)
  663. : res.body
  664. assert.ok(body, 'response has body')
  665. assert.strictEqual(body.toString('hex'), buf.toString('hex'))
  666. }
  667. }