-
-
Notifications
You must be signed in to change notification settings - Fork 5.8k
/
static.ts
219 lines (193 loc) · 6.17 KB
/
static.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
import path from 'path'
import type { OutgoingHttpHeaders, ServerResponse } from 'http'
import type { Options } from 'sirv'
import sirv from 'sirv'
import type { Connect } from 'types/connect'
import micromatch from 'micromatch'
import type { ViteDevServer } from '../..'
import { FS_PREFIX } from '../../constants'
import {
cleanUrl,
fsPathFromId,
fsPathFromUrl,
isFileReadable,
isImportRequest,
isInternalRequest,
isParentDirectory,
isWindows,
slash
} from '../../utils'
const { isMatch } = micromatch
const sirvOptions = (headers?: OutgoingHttpHeaders): Options => {
return {
dev: true,
etag: true,
extensions: [],
setHeaders(res, pathname) {
// Matches js, jsx, ts, tsx.
// The reason this is done, is that the .ts file extension is reserved
// for the MIME type video/mp2t. In almost all cases, we can expect
// these files to be TypeScript files, and for Vite to serve them with
// this Content-Type.
if (/\.[tj]sx?$/.test(pathname)) {
res.setHeader('Content-Type', 'application/javascript')
}
if (headers) {
for (const name in headers) {
res.setHeader(name, headers[name]!)
}
}
}
}
}
export function servePublicMiddleware(
dir: string,
headers?: OutgoingHttpHeaders
): Connect.NextHandleFunction {
const serve = sirv(dir, sirvOptions(headers))
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
return function viteServePublicMiddleware(req, res, next) {
// skip import request and internal requests `/@fs/ /@vite-client` etc...
if (isImportRequest(req.url!) || isInternalRequest(req.url!)) {
return next()
}
serve(req, res, next)
}
}
export function serveStaticMiddleware(
dir: string,
server: ViteDevServer
): Connect.NextHandleFunction {
const serve = sirv(dir, sirvOptions(server.config.server.headers))
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
return function viteServeStaticMiddleware(req, res, next) {
// only serve the file if it's not an html request or ends with `/`
// so that html requests can fallthrough to our html middleware for
// special processing
// also skip internal requests `/@fs/ /@vite-client` etc...
const cleanedUrl = cleanUrl(req.url!)
if (
cleanedUrl.endsWith('/') ||
path.extname(cleanedUrl) === '.html' ||
isInternalRequest(req.url!)
) {
return next()
}
const url = decodeURI(req.url!)
// apply aliases to static requests as well
let redirected: string | undefined
for (const { find, replacement } of server.config.resolve.alias) {
const matches =
typeof find === 'string' ? url.startsWith(find) : find.test(url)
if (matches) {
redirected = url.replace(find, replacement)
break
}
}
if (redirected) {
// dir is pre-normalized to posix style
if (redirected.startsWith(dir)) {
redirected = redirected.slice(dir.length)
}
}
const resolvedUrl = redirected || url
let fileUrl = path.resolve(dir, resolvedUrl.replace(/^\//, ''))
if (resolvedUrl.endsWith('/') && !fileUrl.endsWith('/')) {
fileUrl = fileUrl + '/'
}
if (!ensureServingAccess(fileUrl, server, res, next)) {
return
}
if (redirected) {
req.url = redirected
}
serve(req, res, next)
}
}
export function serveRawFsMiddleware(
server: ViteDevServer
): Connect.NextHandleFunction {
const serveFromRoot = sirv('/', sirvOptions(server.config.server.headers))
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
return function viteServeRawFsMiddleware(req, res, next) {
let url = decodeURI(req.url!)
// In some cases (e.g. linked monorepos) files outside of root will
// reference assets that are also out of served root. In such cases
// the paths are rewritten to `/@fs/` prefixed paths and must be served by
// searching based from fs root.
if (url.startsWith(FS_PREFIX)) {
// restrict files outside of `fs.allow`
if (
!ensureServingAccess(
slash(path.resolve(fsPathFromId(url))),
server,
res,
next
)
) {
return
}
url = url.slice(FS_PREFIX.length)
if (isWindows) url = url.replace(/^[A-Z]:/i, '')
req.url = url
serveFromRoot(req, res, next)
} else {
next()
}
}
}
const _matchOptions = { matchBase: true }
export function isFileServingAllowed(
url: string,
server: ViteDevServer
): boolean {
if (!server.config.server.fs.strict) return true
const file = fsPathFromUrl(url)
if (server.config.server.fs.deny.some((i) => isMatch(file, i, _matchOptions)))
return false
if (server.moduleGraph.safeModulesPath.has(file)) return true
if (server.config.server.fs.allow.some((dir) => isParentDirectory(dir, file)))
return true
return false
}
function ensureServingAccess(
url: string,
server: ViteDevServer,
res: ServerResponse,
next: Connect.NextFunction
): boolean {
if (isFileServingAllowed(url, server)) {
return true
}
if (isFileReadable(cleanUrl(url))) {
const urlMessage = `The request url "${url}" is outside of Vite serving allow list.`
const hintMessage = `
${server.config.server.fs.allow.map((i) => `- ${i}`).join('\n')}
Refer to docs https://vitejs.dev/config/#server-fs-allow for configurations and more details.`
server.config.logger.error(urlMessage)
server.config.logger.warnOnce(hintMessage + '\n')
res.statusCode = 403
res.write(renderRestrictedErrorHTML(urlMessage + '\n' + hintMessage))
res.end()
} else {
// if the file doesn't exist, we shouldn't restrict this path as it can
// be an API call. Middlewares would issue a 404 if the file isn't handled
next()
}
return false
}
function renderRestrictedErrorHTML(msg: string): string {
// to have syntax highlighting and autocompletion in IDE
const html = String.raw
return html`
<body>
<h1>403 Restricted</h1>
<p>${msg.replace(/\n/g, '<br/>')}</p>
<style>
body {
padding: 1em 2em;
}
</style>
</body>
`
}