@@ -3,7 +3,8 @@ import type { Connect } from 'dep-types/connect'
3
3
import type { ResolvedConfig } from '../../config'
4
4
import type { ResolvedPreviewOptions , ResolvedServerOptions } from '../..'
5
5
6
- const allowedHostsCache = new WeakMap < ResolvedConfig , Set < string > > ( )
6
+ const allowedHostsServerCache = new WeakMap < ResolvedConfig , Set < string > > ( )
7
+ const allowedHostsPreviewCache = new WeakMap < ResolvedConfig , Set < string > > ( )
7
8
8
9
const isFileOrExtensionProtocolRE = / ^ (?: f i l e | .+ - e x t e n s i o n ) : / i
9
10
@@ -118,48 +119,59 @@ export function isHostAllowedWithoutCache(
118
119
119
120
/**
120
121
* @param config resolved config
122
+ * @param isPreview whether it's for the preview server or not
121
123
* @param host the value of host header. See [RFC 9110 7.2](https://datatracker.ietf.org/doc/html/rfc9110#name-host-and-authority).
122
124
*/
123
- export function isHostAllowed ( config : ResolvedConfig , host : string ) : boolean {
124
- if ( config . server . allowedHosts === true ) {
125
+ export function isHostAllowed (
126
+ config : ResolvedConfig ,
127
+ isPreview : boolean ,
128
+ host : string ,
129
+ ) : boolean {
130
+ const allowedHosts = isPreview
131
+ ? config . preview . allowedHosts
132
+ : config . server . allowedHosts
133
+ if ( allowedHosts === true ) {
125
134
return true
126
135
}
127
136
128
- if ( ! allowedHostsCache . has ( config ) ) {
129
- allowedHostsCache . set ( config , new Set ( ) )
137
+ const cache = isPreview ? allowedHostsPreviewCache : allowedHostsServerCache
138
+ if ( ! cache . has ( config ) ) {
139
+ cache . set ( config , new Set ( ) )
130
140
}
131
141
132
- const allowedHosts = allowedHostsCache . get ( config ) !
133
- if ( allowedHosts . has ( host ) ) {
142
+ const cachedAllowedHosts = cache . get ( config ) !
143
+ if ( cachedAllowedHosts . has ( host ) ) {
134
144
return true
135
145
}
136
146
137
147
const result = isHostAllowedWithoutCache (
138
- config . server . allowedHosts ,
148
+ allowedHosts ,
139
149
config . additionalAllowedHosts ,
140
150
host ,
141
151
)
142
152
if ( result ) {
143
- allowedHosts . add ( host )
153
+ cachedAllowedHosts . add ( host )
144
154
}
145
155
return result
146
156
}
147
157
148
158
export function hostCheckMiddleware (
149
159
config : ResolvedConfig ,
160
+ isPreview : boolean ,
150
161
) : Connect . NextHandleFunction {
151
162
// Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
152
163
return function viteHostCheckMiddleware ( req , res , next ) {
153
164
const hostHeader = req . headers . host
154
- if ( ! hostHeader || ! isHostAllowed ( config , hostHeader ) ) {
165
+ if ( ! hostHeader || ! isHostAllowed ( config , isPreview , hostHeader ) ) {
155
166
const hostname = hostHeader ?. replace ( / : \d + $ / , '' )
156
167
const hostnameWithQuotes = JSON . stringify ( hostname )
168
+ const optionName = `${ isPreview ? 'preview' : 'server' } .allowedHosts`
157
169
res . writeHead ( 403 , {
158
170
'Content-Type' : 'text/plain' ,
159
171
} )
160
172
res . end (
161
173
`Blocked request. This host (${ hostnameWithQuotes } ) is not allowed.\n` +
162
- `To allow this host, add ${ hostnameWithQuotes } to \`server.allowedHosts \` in vite.config.js.` ,
174
+ `To allow this host, add ${ hostnameWithQuotes } to \`${ optionName } \` in vite.config.js.` ,
163
175
)
164
176
return
165
177
}
0 commit comments