feat: CtLayout01 커스텀 콘텐츠 기능 개선
- fnCustomVideo 유튜브 팝업 함수 추가 - script/link exclude 속성 지원 - 전역 함수 등록 로직 통합 (registerGlobalFunctions) - CSS Selector injection 보안 취약점 수정 - 사용되지 않는 변수/props 제거 - DOMPurify exclude, defer 속성 허용 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,12 +1,7 @@
|
||||
import { LRUCache } from 'lru-cache'
|
||||
import { defineEventHandler, createError, type H3Event } from 'h3'
|
||||
import {
|
||||
getGameDomain,
|
||||
getPathLocale,
|
||||
getIntroRedirectPath,
|
||||
isExternalUrl,
|
||||
getPathAfterLanguage,
|
||||
} from '#layers/utils/urlUtil'
|
||||
import { ISO_LANGUAGE_CODES } from '@/i18n.config'
|
||||
import { getGameDomain, getPathLocale, parseUrl } from '#layers/utils/urlUtil'
|
||||
import { ssrGetFinalLocale } from '#layers/utils/localeUtil'
|
||||
import { isStaticFile } from '#layers/utils/commonUtil'
|
||||
import { getTrueClientIp } from '#layers/utils/apiUtil'
|
||||
@@ -73,41 +68,41 @@ const cache = new LRUCache<string, WebInspectionData>({
|
||||
|
||||
/**
|
||||
* Locale Middleware 역할 함수
|
||||
*
|
||||
* @param event - 이벤트 객체
|
||||
* @param finalLocale - 최종 언어
|
||||
* URL의 언어 코드를 최종 언어로 변경하거나 추가
|
||||
*/
|
||||
function fnLocaleMiddleware(event: H3Event, finalLocale: string) {
|
||||
function fnLocaleMiddleware(
|
||||
event: H3Event,
|
||||
finalLocale: string,
|
||||
langCodes: string[],
|
||||
intro: string
|
||||
): void {
|
||||
// 이미 응답이 종료되었는지 확인
|
||||
if (event.node.res.headersSent || event.node.res.writableEnded) {
|
||||
return
|
||||
}
|
||||
|
||||
const path = event?.node.req.url || ''
|
||||
const [pathPart, queryString = ''] = path.split('?')
|
||||
const arrPath = pathPart.split('/')
|
||||
const finalPath = parseUrl(
|
||||
path,
|
||||
finalLocale,
|
||||
langCodes,
|
||||
ISO_LANGUAGE_CODES,
|
||||
intro
|
||||
)
|
||||
|
||||
// 최종 언어 세팅된 경로 생성
|
||||
const pathLocale = arrPath.length > 1 ? arrPath[1] : ''
|
||||
|
||||
// URL에서 현재 언어와 최종 언어가 다르면 리다이렉트
|
||||
if (pathLocale !== finalLocale) {
|
||||
let newLocalePath = ''
|
||||
if (pathLocale === '') {
|
||||
newLocalePath = `/${finalLocale}`
|
||||
} else {
|
||||
arrPath[1] = finalLocale
|
||||
newLocalePath = arrPath.join('/')
|
||||
}
|
||||
|
||||
if (queryString) {
|
||||
newLocalePath += `?${queryString}`
|
||||
}
|
||||
|
||||
event.node.res.statusCode = 302
|
||||
event.node.res.setHeader('Location', newLocalePath)
|
||||
event.node.res.end()
|
||||
// finalPath가 유효하지 않으면 리다이렉트하지 않음
|
||||
if (!finalPath || typeof finalPath !== 'string') {
|
||||
return
|
||||
}
|
||||
|
||||
// 현재 경로와 최종 경로가 같으면 리다이렉트 불필요
|
||||
if (path === finalPath) {
|
||||
return
|
||||
}
|
||||
|
||||
event.node.res.statusCode = 302
|
||||
event.node.res.setHeader('Location', finalPath)
|
||||
event.node.res.end()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -144,6 +139,7 @@ export default defineEventHandler(async event => {
|
||||
let gameDataResponse: GameDataResponse | null = null
|
||||
let gameDataLangCodes: string[] | null = null
|
||||
let gameDataDefaultLocale: string | null = null
|
||||
let gameDataIntro: string | null = null
|
||||
let finalLocale: string
|
||||
|
||||
try {
|
||||
@@ -160,6 +156,7 @@ export default defineEventHandler(async event => {
|
||||
gameDataResponse = response
|
||||
gameDataLangCodes = response?.value?.lang_codes || null
|
||||
gameDataDefaultLocale = response?.value?.default_lang_code || null
|
||||
gameDataIntro = response?.value?.intro?.page_url || ''
|
||||
event.context.gameDomain = gameDomain
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
@@ -263,7 +260,12 @@ export default defineEventHandler(async event => {
|
||||
// -------------------------------------------------------------------------------
|
||||
// [Locale Middleware]
|
||||
// -------------------------------------------------------------------------------
|
||||
fnLocaleMiddleware(event, finalLocale)
|
||||
fnLocaleMiddleware(
|
||||
event,
|
||||
finalLocale,
|
||||
gameDataLangCodes,
|
||||
gameDataIntro
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (!event.node.res.headersSent && !event.node.res.writableEnded) {
|
||||
@@ -302,40 +304,7 @@ export default defineEventHandler(async event => {
|
||||
// -------------------------------------------------------------------------------
|
||||
// [Locale Middleware]
|
||||
// -------------------------------------------------------------------------------
|
||||
fnLocaleMiddleware(event, finalLocale)
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// [Home Redirect]
|
||||
// -------------------------------------------------------------------------------
|
||||
if (!event.node.res.headersSent && !event.node.res.writableEnded) {
|
||||
const gamePath = getPathAfterLanguage(fullPath)
|
||||
const isRootPath = gamePath === '' || gamePath === '/'
|
||||
|
||||
if (isRootPath) {
|
||||
// intro.page_url이 있으면 해당 URL로 리다이렉트, 없으면 /home으로
|
||||
const introPageUrl = gameDataValue?.intro?.page_url
|
||||
const currentFullUrl = event.node.req.url || fullPath
|
||||
const redirectPath = getIntroRedirectPath(
|
||||
introPageUrl,
|
||||
finalLocale,
|
||||
currentFullUrl
|
||||
)
|
||||
|
||||
// 무한 리다이렉트 방지: 현재 경로와 리다이렉트할 URL 비교
|
||||
const currentPathOnly = fullPath.split('?')[0] // 현재 경로 (쿼리스트링 제외)
|
||||
const normalizedFinalUrl = redirectPath.split('?')[0] // 리다이렉트 경로 (쿼리스트링 제외)
|
||||
const isExternal = isExternalUrl(redirectPath)
|
||||
const isSamePath =
|
||||
!isExternal && currentPathOnly === normalizedFinalUrl
|
||||
|
||||
if (!isSamePath) {
|
||||
// 다른 경로에서 접근한 경우에만 리다이렉트
|
||||
event.node.res.statusCode = 302
|
||||
event.node.res.setHeader('Location', redirectPath)
|
||||
event.node.res.end()
|
||||
}
|
||||
}
|
||||
}
|
||||
fnLocaleMiddleware(event, finalLocale, gameDataLangCodes, gameDataIntro)
|
||||
}
|
||||
|
||||
// 정상 접속 허용
|
||||
|
||||
Reference in New Issue
Block a user