csrf(state) 관련 취약점 탐지 기능 추가

This commit is contained in:
tv0924@icloud.com 2025-05-28 14:11:53 +09:00
commit e868cbec67
5 changed files with 400 additions and 7 deletions

View file

@ -0,0 +1,193 @@
let instance: HttpUtils | null = null;
export class HttpUtils {
/**
* .
*/
public constructor() {
if (instance) {
return instance;
}
instance = this;
return instance;
}
/**
* .
* @param headers - Record<string, string | string[]>
* @returns -
*/
lowerCaseAllHeaders(
headers: Record<string, string | string[]>
): Record<string, string | string[]> {
const result: Record<string, string | string[]> = {};
for (const [rawKey, rawValue] of Object.entries(headers)) {
const key = rawKey.toLowerCase();
if (Array.isArray(rawValue)) {
// 배열이면 각 요소를 소문자로
result[key] = rawValue.map((v) => v.toLowerCase());
} else {
// 단일 문자열이면 바로 소문자로
result[key] = rawValue.toLowerCase();
}
}
return result;
}
getQueryParamFromURI(uri: string, key: string): string | null {
uri = uri.toLowerCase();
key = key.toLowerCase();
try {
const urlObj = new URL(uri);
return urlObj.searchParams.get(key);
} catch (e) {
return null;
}
}
// Query
/**
* (query) key에 .
* @param query - "a=1&b=2..." ( ? )
* @param key -
* @returns - , null
*/
getQueryParam(query: string, key: string): string | null {
query = query.toLowerCase();
key = key.toLowerCase();
const params = new URLSearchParams(query);
return params.get(key);
}
/**
* (query) key=value를 , .
* - key가 (set), .
* @param query - "a=1&b=2..." ( ? )
* @param key -
* @param value -
* @returns - "a=1&b=2&c=3..."
*/
setQueryParam(query: string, key: string, value: string): string {
query = query.toLowerCase();
key = key.toLowerCase();
value = value.toLowerCase();
const params = new URLSearchParams(query);
params.set(key, value);
return params.toString();
}
/**
* (query) key에 (delete),
* .
* @param query - "a=1&b=2..." ( ? )
* @param key -
* @returns -
*/
removeQueryParam(query: string, key: string): string {
query = query.toLowerCase();
key = key.toLowerCase();
const params = new URLSearchParams(query);
params.delete(key);
return params.toString();
}
// Headers
/**
* name에 .
* @param headers - Response.getHeaders()
* @param name - (: "location", "Content-Type")
* @returns - , null
*/
getHeaderValue(
headers: Record<string, string | string[]>,
name: string
): string | null {
headers = this.lowerCaseAllHeaders(headers);
const target = name.toLowerCase();
for (const [key, value] of Object.entries(headers)) {
if (key.toLowerCase() === target) {
if (Array.isArray(value)) {
// 배열 형태일 때 첫 번째 요소가 비어있을 수도 있으니 안전하게 처리
return value.length > 0 &&
value[0] !== undefined &&
value[0].length > 0
? value[0]
: null;
}
// 문자열일 때
return value.length > 0 ? value : null;
}
}
return null;
}
/**
* name에 value로 .
* - .
* - value가 string인 [value] , string[] .
* - .
*
* @param headers - ,
* @param name - (: "Authorization", "X-Custom-Header")
* @param value - (string string[])
* @returns -
*/
setHeaderValue(
headers: Record<string, string | string[]>,
name: string,
value: string | string[]
): Record<string, string[]> {
headers = this.lowerCaseAllHeaders(headers);
const lowerName = name.toLowerCase();
const newHeaders: Record<string, string[]> = {};
// 1) 기존 헤더 복사하되, name과 일치하는 항목은 value로 대체
for (const [key, vals] of Object.entries(headers)) {
if (key.toLowerCase() === lowerName) {
newHeaders[key] = Array.isArray(value) ? value : [value];
} else {
newHeaders[key] = Array.isArray(vals) ? vals : [vals];
}
}
// 2) 해당 헤더가 원래 없었다면 새로 추가
const exists = Object.keys(newHeaders).some(
(k) => k.toLowerCase() === lowerName
);
if (!exists) {
newHeaders[name] = Array.isArray(value) ? value : [value];
}
return newHeaders;
}
/**
* () .
* @param headers - ,
* @param namesToRemove - ( ). .
* @returns -
*/
removeHeaders(
headers: Record<string, string | string[]>,
namesToRemove: string | string[]
): Record<string, string[]> {
headers = this.lowerCaseAllHeaders(headers);
const toRemove = Array.isArray(namesToRemove)
? namesToRemove.map((n) => n.toLowerCase())
: [namesToRemove.toLowerCase()];
const filtered: Record<string, string[]> = {};
for (const [key, vals] of Object.entries(headers)) {
if (!toRemove.includes(key.toLowerCase())) {
filtered[key] = Array.isArray(vals) ? vals : [vals];
}
}
return filtered;
}
}