服务器配置
This commit is contained in:
1 parent
a1b4a63552
commit
fa7c8244b0
33 files changed
+1882
-363
No files matched your search
+160
-20
@@ -2,10 +2,16 @@ import type { H3Event } from 'h3'
|
||||
import {
|
||||
getImageBaseUrlConfigured,
|
||||
getSiteBaseUrlConfigured,
|
||||
getVideoBaseUrlConfigured,
|
||||
getFileBaseUrlConfigured,
|
||||
hostnameFromBaseUrl,
|
||||
isFileDomainSeparated,
|
||||
isImageDomainSeparated,
|
||||
isDomainSeparationActive,
|
||||
isHideFolderInUrl
|
||||
isHideFolderInUrl,
|
||||
isVideoDomainSeparated
|
||||
} from './env'
|
||||
import { validateFileKey, validateImageKey, validateStorageKey, validateVideoKey } from './image-key'
|
||||
import { requestPathToImageKey } from './serve-image'
|
||||
|
||||
const DEV_LOCAL_HOSTS = new Set(['localhost', '127.0.0.1'])
|
||||
@@ -22,15 +28,34 @@ function isDevLocalHost(host: string): boolean {
|
||||
return process.env.NODE_ENV === 'development' && DEV_LOCAL_HOSTS.has(host)
|
||||
}
|
||||
|
||||
function configuredHostOrNull(loader: () => string | null): string | null {
|
||||
try {
|
||||
return loader()
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export function getConfiguredSiteHost(event: H3Event): string | null {
|
||||
return hostnameFromBaseUrl(getSiteBaseUrlConfigured(event))
|
||||
}
|
||||
|
||||
export function getConfiguredImageHost(event: H3Event): string | null {
|
||||
if (!isImageDomainSeparated(event)) return null
|
||||
return hostnameFromBaseUrl(getImageBaseUrlConfigured(event))
|
||||
}
|
||||
|
||||
/** 双域名开启时,拒绝未配置的第三 Host(开发环境 localhost 例外) */
|
||||
export function getConfiguredVideoHost(event: H3Event): string | null {
|
||||
if (!isVideoDomainSeparated(event)) return null
|
||||
return hostnameFromBaseUrl(getVideoBaseUrlConfigured(event))
|
||||
}
|
||||
|
||||
export function getConfiguredFileHost(event: H3Event): string | null {
|
||||
if (!isFileDomainSeparated(event)) return null
|
||||
return hostnameFromBaseUrl(getFileBaseUrlConfigured(event))
|
||||
}
|
||||
|
||||
/** 域名分离开启时,拒绝未配置的 Host(开发环境 localhost 例外) */
|
||||
export function shouldBlockUnknownHostRequest(
|
||||
event: H3Event,
|
||||
options?: {
|
||||
@@ -38,6 +63,8 @@ export function shouldBlockUnknownHostRequest(
|
||||
separationActive?: boolean
|
||||
siteHost?: string | null
|
||||
imageHost?: string | null
|
||||
videoHost?: string | null
|
||||
fileHost?: string | null
|
||||
}
|
||||
): boolean {
|
||||
const separationActive = options?.separationActive ?? isDomainSeparationActive(event)
|
||||
@@ -45,15 +72,48 @@ export function shouldBlockUnknownHostRequest(
|
||||
|
||||
const siteHost = options?.siteHost ?? getConfiguredSiteHost(event)
|
||||
const imageHost = options?.imageHost ?? getConfiguredImageHost(event)
|
||||
const videoHost = options?.videoHost
|
||||
?? configuredHostOrNull(() => getConfiguredVideoHost(event))
|
||||
const fileHost = options?.fileHost
|
||||
?? configuredHostOrNull(() => getConfiguredFileHost(event))
|
||||
if (!siteHost || !imageHost) return false
|
||||
|
||||
const requestHost = (options?.requestHost ?? getIsolationRequestHost(event)).toLowerCase()
|
||||
if (requestHost === siteHost || requestHost === imageHost) return false
|
||||
if (
|
||||
requestHost === siteHost
|
||||
|| requestHost === imageHost
|
||||
|| (videoHost && requestHost === videoHost)
|
||||
|| (fileHost && requestHost === fileHost)
|
||||
) return false
|
||||
if (isDevLocalHost(requestHost)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
function requestKey(
|
||||
event: H3Event,
|
||||
options?: {
|
||||
pathname?: string
|
||||
hideFolder?: boolean
|
||||
}
|
||||
): string | null {
|
||||
const pathname = options?.pathname ?? getRequestURL(event, {
|
||||
xForwardedHost: true,
|
||||
xForwardedProto: true
|
||||
}).pathname
|
||||
return requestPathToImageKey(pathname, {
|
||||
hideFolder: options?.hideFolder ?? isHideFolderInUrl(event)
|
||||
})
|
||||
}
|
||||
|
||||
function mediaKindForKey(key: string | null): 'image' | 'video' | 'file' | null {
|
||||
if (!key || !validateStorageKey(key)) return null
|
||||
if (validateImageKey(key)) return 'image'
|
||||
if (validateVideoKey(key)) return 'video'
|
||||
if (validateFileKey(key)) return 'file'
|
||||
return null
|
||||
}
|
||||
|
||||
export function shouldBlockImageHostRequest(
|
||||
event: H3Event,
|
||||
options?: {
|
||||
@@ -63,6 +123,8 @@ export function shouldBlockImageHostRequest(
|
||||
separationActive?: boolean
|
||||
imageHost?: string | null
|
||||
siteHost?: string | null
|
||||
videoHost?: string | null
|
||||
fileHost?: string | null
|
||||
hideFolder?: boolean
|
||||
}
|
||||
): boolean {
|
||||
@@ -74,21 +136,85 @@ export function shouldBlockImageHostRequest(
|
||||
|
||||
const requestHost = (options?.requestHost ?? getIsolationRequestHost(event)).toLowerCase()
|
||||
if (requestHost !== imageHost) return false
|
||||
const siteHost = options?.siteHost ?? configuredHostOrNull(() => getConfiguredSiteHost(event))
|
||||
if (siteHost === imageHost) return false
|
||||
|
||||
const method = (options?.method ?? getMethod(event)).toUpperCase()
|
||||
if (method !== 'GET' && method !== 'HEAD') return true
|
||||
|
||||
const pathname = options?.pathname ?? getRequestURL(event, {
|
||||
xForwardedHost: true,
|
||||
xForwardedProto: true
|
||||
}).pathname
|
||||
const hideFolder = options?.hideFolder ?? isHideFolderInUrl(event)
|
||||
return requestPathToImageKey(pathname, {
|
||||
hideFolder
|
||||
}) === null
|
||||
const kind = mediaKindForKey(requestKey(event, options))
|
||||
if (kind === 'image') return false
|
||||
const videoHost = options?.videoHost
|
||||
?? configuredHostOrNull(() => getConfiguredVideoHost(event))
|
||||
const fileHost = options?.fileHost
|
||||
?? configuredHostOrNull(() => getConfiguredFileHost(event))
|
||||
if (kind === 'video' && (!videoHost || videoHost === imageHost)) return false
|
||||
if (kind === 'file' && (!fileHost || fileHost === imageHost)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
/** 网站域禁止直链出图(仅图片域可 GET/HEAD 图片路径) */
|
||||
function shouldBlockMediaHostRequest(
|
||||
event: H3Event,
|
||||
expectedKind: 'video' | 'file',
|
||||
configuredHost: string | null,
|
||||
options?: {
|
||||
method?: string
|
||||
pathname?: string
|
||||
requestHost?: string
|
||||
separationActive?: boolean
|
||||
hideFolder?: boolean
|
||||
}
|
||||
): boolean {
|
||||
const separationActive = options?.separationActive ?? isDomainSeparationActive(event)
|
||||
if (!separationActive || !configuredHost) return false
|
||||
const siteHost = configuredHostOrNull(() => getConfiguredSiteHost(event))
|
||||
if (siteHost === configuredHost) return false
|
||||
const requestHost = (options?.requestHost ?? getIsolationRequestHost(event)).toLowerCase()
|
||||
if (requestHost !== configuredHost) return false
|
||||
const method = (options?.method ?? getMethod(event)).toUpperCase()
|
||||
if (method !== 'GET' && method !== 'HEAD') return true
|
||||
return mediaKindForKey(requestKey(event, options)) !== expectedKind
|
||||
}
|
||||
|
||||
export function shouldBlockVideoHostRequest(
|
||||
event: H3Event,
|
||||
options?: {
|
||||
method?: string
|
||||
pathname?: string
|
||||
requestHost?: string
|
||||
separationActive?: boolean
|
||||
videoHost?: string | null
|
||||
hideFolder?: boolean
|
||||
}
|
||||
): boolean {
|
||||
return shouldBlockMediaHostRequest(
|
||||
event,
|
||||
'video',
|
||||
options?.videoHost ?? getConfiguredVideoHost(event),
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
export function shouldBlockFileHostRequest(
|
||||
event: H3Event,
|
||||
options?: {
|
||||
method?: string
|
||||
pathname?: string
|
||||
requestHost?: string
|
||||
separationActive?: boolean
|
||||
fileHost?: string | null
|
||||
hideFolder?: boolean
|
||||
}
|
||||
): boolean {
|
||||
return shouldBlockMediaHostRequest(
|
||||
event,
|
||||
'file',
|
||||
options?.fileHost ?? getConfiguredFileHost(event),
|
||||
options
|
||||
)
|
||||
}
|
||||
|
||||
/** 网站域禁止直接访问任何媒体路径(仅对应媒体域可 GET/HEAD) */
|
||||
export function shouldBlockSiteHostImageRequest(
|
||||
event: H3Event,
|
||||
options?: {
|
||||
@@ -97,6 +223,9 @@ export function shouldBlockSiteHostImageRequest(
|
||||
requestHost?: string
|
||||
separationActive?: boolean
|
||||
siteHost?: string | null
|
||||
imageHost?: string | null
|
||||
videoHost?: string | null
|
||||
fileHost?: string | null
|
||||
hideFolder?: boolean
|
||||
}
|
||||
): boolean {
|
||||
@@ -112,12 +241,23 @@ export function shouldBlockSiteHostImageRequest(
|
||||
const method = (options?.method ?? getMethod(event)).toUpperCase()
|
||||
if (method !== 'GET' && method !== 'HEAD') return false
|
||||
|
||||
const pathname = options?.pathname ?? getRequestURL(event, {
|
||||
xForwardedHost: true,
|
||||
xForwardedProto: true
|
||||
}).pathname
|
||||
const hideFolder = options?.hideFolder ?? isHideFolderInUrl(event)
|
||||
return requestPathToImageKey(pathname, {
|
||||
hideFolder
|
||||
}) !== null
|
||||
const kind = mediaKindForKey(requestKey(event, options))
|
||||
if (!kind) return false
|
||||
if (kind === 'image') {
|
||||
const imageHost = options?.imageHost
|
||||
?? configuredHostOrNull(() => getConfiguredImageHost(event))
|
||||
return imageHost !== siteHost
|
||||
}
|
||||
if (kind === 'video') {
|
||||
const imageHost = options?.imageHost
|
||||
?? configuredHostOrNull(() => getConfiguredImageHost(event))
|
||||
const videoHost = options?.videoHost
|
||||
?? configuredHostOrNull(() => getConfiguredVideoHost(event))
|
||||
return (videoHost || imageHost) !== siteHost
|
||||
}
|
||||
const imageHost = options?.imageHost
|
||||
?? configuredHostOrNull(() => getConfiguredImageHost(event))
|
||||
const fileHost = options?.fileHost
|
||||
?? configuredHostOrNull(() => getConfiguredFileHost(event))
|
||||
return (fileHost || imageHost) !== siteHost
|
||||
}
|
||||
Reference in new issue
Block a user