1 | 'use strict'
|
2 |
|
3 | const fs = require('fs')
|
4 | const mime = require('../detect/mime')
|
5 | const path = require('path')
|
6 | const util = require('util')
|
7 |
|
8 | const defaultMimeType = mime.getType('bin')
|
9 | const statAsync = util.promisify(fs.stat)
|
10 | const readdirAsync = util.promisify(fs.readdir)
|
11 |
|
12 | function sendFile (response, filePath, stat) {
|
13 | return new Promise((resolve, reject) => {
|
14 | response.writeHead(200, {
|
15 | 'Content-Type': mime.getType(path.extname(filePath)) || defaultMimeType,
|
16 | 'Content-Length': stat.size
|
17 | })
|
18 | fs.createReadStream(filePath)
|
19 | .on('error', reject)
|
20 | .on('end', resolve)
|
21 | .pipe(response)
|
22 | })
|
23 | }
|
24 |
|
25 | function sendIndex (response, folderPath) {
|
26 | const indexPath = path.join(folderPath, 'index.html')
|
27 | return statAsync(indexPath)
|
28 | .then(stat => sendFile(response, indexPath, stat))
|
29 | }
|
30 |
|
31 | async function checkCaseSensitivePath (filePath) {
|
32 | const folderPath = path.dirname(filePath)
|
33 | if (folderPath && folderPath !== filePath) {
|
34 | const name = path.basename(filePath)
|
35 | const names = await readdirAsync(folderPath)
|
36 | if (!names.includes(name)) {
|
37 | throw new Error('Not found')
|
38 | }
|
39 | return checkCaseSensitivePath(folderPath)
|
40 | }
|
41 | }
|
42 |
|
43 | module.exports = {
|
44 | schema: {
|
45 | 'case-sensitive': {
|
46 | type: 'boolean',
|
47 | defaultValue: false
|
48 | },
|
49 | 'ignore-if-not-found': {
|
50 | type: 'boolean',
|
51 | defaultValue: false
|
52 | }
|
53 | },
|
54 | method: 'GET',
|
55 | redirect: ({ request, mapping, redirect, response }) => {
|
56 | let filePath = /([^?#]+)/.exec(unescape(redirect))[1]
|
57 | if (!path.isAbsolute(filePath)) {
|
58 | filePath = path.join(mapping.cwd, filePath)
|
59 | }
|
60 | const directoryAccess = !!filePath.match(/(\\|\/)$/)
|
61 | if (directoryAccess) {
|
62 | filePath = filePath.substring(0, filePath.length - 1)
|
63 | }
|
64 | return statAsync(filePath)
|
65 | .then(async stat => {
|
66 | if (mapping['case-sensitive']) {
|
67 | await checkCaseSensitivePath(filePath)
|
68 | }
|
69 | const isDirectory = stat.isDirectory()
|
70 | if (isDirectory ^ directoryAccess) {
|
71 | return 404
|
72 | }
|
73 | if (isDirectory) {
|
74 | return sendIndex(response, filePath)
|
75 | }
|
76 | return sendFile(response, filePath, stat)
|
77 | })
|
78 | .catch(() => {
|
79 | if (!mapping['ignore-if-not-found']) {
|
80 | return 404
|
81 | }
|
82 | })
|
83 | }
|
84 | }
|