@@ -3,7 +3,8 @@ import type { Connect } from 'dep-types/connect'
33import type { ResolvedConfig } from '../../config'
44import type { ResolvedPreviewOptions , ResolvedServerOptions } from '../..'
55
6- const allowedHostsCache = new WeakMap < ResolvedConfig , Set < string > > ( )
6+ const allowedHostsServerCache = new WeakMap < ResolvedConfig , Set < string > > ( )
7+ const allowedHostsPreviewCache = new WeakMap < ResolvedConfig , Set < string > > ( )
78
89const isFileOrExtensionProtocolRE = / ^ (?: f i l e | .+ - e x t e n s i o n ) : / i
910
@@ -118,48 +119,59 @@ export function isHostAllowedWithoutCache(
118119
119120/**
120121 * @param config resolved config
122+ * @param isPreview whether it's for the preview server or not
121123 * @param host the value of host header. See [RFC 9110 7.2](https://datatracker.ietf.org/doc/html/rfc9110#name-host-and-authority).
122124 */
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 ) {
125134 return true
126135 }
127136
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 ( ) )
130140 }
131141
132- const allowedHosts = allowedHostsCache . get ( config ) !
133- if ( allowedHosts . has ( host ) ) {
142+ const cachedAllowedHosts = cache . get ( config ) !
143+ if ( cachedAllowedHosts . has ( host ) ) {
134144 return true
135145 }
136146
137147 const result = isHostAllowedWithoutCache (
138- config . server . allowedHosts ?? [ ] ,
148+ allowedHosts ?? [ ] ,
139149 config . additionalAllowedHosts ,
140150 host ,
141151 )
142152 if ( result ) {
143- allowedHosts . add ( host )
153+ cachedAllowedHosts . add ( host )
144154 }
145155 return result
146156}
147157
148158export function hostCheckMiddleware (
149159 config : ResolvedConfig ,
160+ isPreview : boolean ,
150161) : Connect . NextHandleFunction {
151162 // Keep the named function. The name is visible in debug logs via `DEBUG=connect:dispatcher ...`
152163 return function viteHostCheckMiddleware ( req , res , next ) {
153164 const hostHeader = req . headers . host
154- if ( ! hostHeader || ! isHostAllowed ( config , hostHeader ) ) {
165+ if ( ! hostHeader || ! isHostAllowed ( config , isPreview , hostHeader ) ) {
155166 const hostname = hostHeader ?. replace ( / : \d + $ / , '' )
156167 const hostnameWithQuotes = JSON . stringify ( hostname )
168+ const optionName = `${ isPreview ? 'preview' : 'server' } .allowedHosts`
157169 res . writeHead ( 403 , {
158170 'Content-Type' : 'text/plain' ,
159171 } )
160172 res . end (
161173 `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.` ,
163175 )
164176 return
165177 }
0 commit comments