viewlogic 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/viewlogic-router.umd.js +1 -1
- package/package.json +1 -2
- package/src/core/ComponentLoader.js +0 -182
- package/src/core/ErrorHandler.js +0 -331
- package/src/core/RouteLoader.js +0 -371
- package/src/plugins/AuthManager.js +0 -505
- package/src/plugins/CacheManager.js +0 -352
- package/src/plugins/I18nManager.js +0 -508
- package/src/plugins/QueryManager.js +0 -402
- package/src/viewlogic-router.js +0 -481
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ViewLogic Query Management System
|
|
3
|
-
* URL 쿼리 파라미터 관리 시스템
|
|
4
|
-
*/
|
|
5
|
-
export class QueryManager {
|
|
6
|
-
constructor(router, options = {}) {
|
|
7
|
-
this.config = {
|
|
8
|
-
enableParameterValidation: options.enableParameterValidation !== false,
|
|
9
|
-
logSecurityWarnings: options.logSecurityWarnings !== false,
|
|
10
|
-
maxParameterLength: options.maxParameterLength || 1000,
|
|
11
|
-
maxArraySize: options.maxArraySize || 100,
|
|
12
|
-
maxParameterCount: options.maxParameterCount || 50,
|
|
13
|
-
allowedKeyPattern: options.allowedKeyPattern || /^[a-zA-Z0-9_\-]+$/,
|
|
14
|
-
debug: options.debug || false
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
// 라우터 인스턴스 참조
|
|
18
|
-
this.router = router;
|
|
19
|
-
|
|
20
|
-
// 현재 쿼리 파라미터 상태
|
|
21
|
-
this.currentQueryParams = {};
|
|
22
|
-
|
|
23
|
-
this.log('info', 'QueryManager initialized with config:', this.config);
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* 로깅 래퍼 메서드
|
|
28
|
-
*/
|
|
29
|
-
log(level, ...args) {
|
|
30
|
-
if (this.router?.errorHandler) {
|
|
31
|
-
this.router.errorHandler.log(level, 'QueryManager', ...args);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 파라미터 값 sanitize (XSS, SQL Injection 방어)
|
|
37
|
-
*/
|
|
38
|
-
sanitizeParameter(value) {
|
|
39
|
-
if (typeof value !== 'string') return value;
|
|
40
|
-
|
|
41
|
-
// XSS 방어: HTML 태그와 스크립트 제거
|
|
42
|
-
let sanitized = value
|
|
43
|
-
.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '') // script 태그 제거
|
|
44
|
-
.replace(/<iframe\b[^<]*(?:(?!<\/iframe>)<[^<]*)*<\/iframe>/gi, '') // iframe 태그 제거
|
|
45
|
-
.replace(/<object\b[^<]*(?:(?!<\/object>)<[^<]*)*<\/object>/gi, '') // object 태그 제거
|
|
46
|
-
.replace(/<embed\b[^<]*(?:(?!<\/embed>)<[^<]*)*<\/embed>/gi, '') // embed 태그 제거
|
|
47
|
-
.replace(/<link\b[^<]*>/gi, '') // link 태그 제거
|
|
48
|
-
.replace(/<meta\b[^<]*>/gi, '') // meta 태그 제거
|
|
49
|
-
.replace(/javascript:/gi, '') // javascript: 프로토콜 제거
|
|
50
|
-
.replace(/vbscript:/gi, '') // vbscript: 프로토콜 제거
|
|
51
|
-
.replace(/data:/gi, '') // data: 프로토콜 제거
|
|
52
|
-
.replace(/on\w+\s*=/gi, '') // 이벤트 핸들러 제거 (onclick, onload 등)
|
|
53
|
-
.replace(/expression\s*\(/gi, '') // CSS expression 제거
|
|
54
|
-
.replace(/url\s*\(/gi, ''); // CSS url() 제거
|
|
55
|
-
|
|
56
|
-
// SQL Injection 방어: 위험한 SQL 키워드 필터링
|
|
57
|
-
const sqlPatterns = [
|
|
58
|
-
/(\b(union|select|insert|update|delete|drop|create|alter|exec|execute|sp_|xp_)\b)/gi,
|
|
59
|
-
/(;|\||&|\*|%|<|>)/g, // 위험한 특수문자
|
|
60
|
-
/(--|\/\*|\*\/)/g, // SQL 주석
|
|
61
|
-
/(\bor\b.*\b=\b|\band\b.*\b=\b)/gi, // OR/AND 조건문
|
|
62
|
-
/('.*'|".*")/g, // 따옴표로 둘러싸인 문자열
|
|
63
|
-
/(\\\w+)/g // 백슬래시 이스케이프
|
|
64
|
-
];
|
|
65
|
-
|
|
66
|
-
for (const pattern of sqlPatterns) {
|
|
67
|
-
sanitized = sanitized.replace(pattern, '');
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
// 추가 보안: 연속된 특수문자 제거
|
|
71
|
-
sanitized = sanitized.replace(/[<>'"&]{2,}/g, '');
|
|
72
|
-
|
|
73
|
-
// 길이 제한 (DoS 방어)
|
|
74
|
-
if (sanitized.length > this.config.maxParameterLength) {
|
|
75
|
-
sanitized = sanitized.substring(0, this.config.maxParameterLength);
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return sanitized.trim();
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 파라미터 유효성 검증
|
|
83
|
-
*/
|
|
84
|
-
validateParameter(key, value) {
|
|
85
|
-
// 보안 검증이 비활성화된 경우 통과
|
|
86
|
-
if (!this.config.enableParameterValidation) {
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
// 파라미터 키 검증
|
|
91
|
-
if (typeof key !== 'string' || key.length === 0) {
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// 키 이름 제한
|
|
96
|
-
if (!this.config.allowedKeyPattern.test(key)) {
|
|
97
|
-
if (this.config.logSecurityWarnings) {
|
|
98
|
-
console.warn(`Invalid parameter key format: ${key}`);
|
|
99
|
-
}
|
|
100
|
-
return false;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// 키 길이 제한
|
|
104
|
-
if (key.length > 50) {
|
|
105
|
-
if (this.config.logSecurityWarnings) {
|
|
106
|
-
console.warn(`Parameter key too long: ${key}`);
|
|
107
|
-
}
|
|
108
|
-
return false;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// 값 타입 검증
|
|
112
|
-
if (value !== null && value !== undefined) {
|
|
113
|
-
if (typeof value === 'string') {
|
|
114
|
-
// 문자열 길이 제한
|
|
115
|
-
if (value.length > this.config.maxParameterLength) {
|
|
116
|
-
if (this.config.logSecurityWarnings) {
|
|
117
|
-
console.warn(`Parameter value too long for key: ${key}`);
|
|
118
|
-
}
|
|
119
|
-
return false;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 위험한 패턴 감지
|
|
123
|
-
const dangerousPatterns = [
|
|
124
|
-
/<script|<iframe|<object|<embed/gi,
|
|
125
|
-
/javascript:|vbscript:|data:/gi,
|
|
126
|
-
/union.*select|insert.*into|delete.*from/gi,
|
|
127
|
-
/\.\.\//g, // 경로 탐색 공격
|
|
128
|
-
/[<>'"&]{3,}/g // 연속된 특수문자
|
|
129
|
-
];
|
|
130
|
-
|
|
131
|
-
for (const pattern of dangerousPatterns) {
|
|
132
|
-
if (pattern.test(value)) {
|
|
133
|
-
if (this.config.logSecurityWarnings) {
|
|
134
|
-
console.warn(`Dangerous pattern detected in parameter ${key}:`, value);
|
|
135
|
-
}
|
|
136
|
-
return false;
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
} else if (Array.isArray(value)) {
|
|
140
|
-
// 배열 길이 제한
|
|
141
|
-
if (value.length > this.config.maxArraySize) {
|
|
142
|
-
if (this.config.logSecurityWarnings) {
|
|
143
|
-
console.warn(`Parameter array too large for key: ${key}`);
|
|
144
|
-
}
|
|
145
|
-
return false;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 배열 각 요소 검증
|
|
149
|
-
for (const item of value) {
|
|
150
|
-
if (!this.validateParameter(`${key}[]`, item)) {
|
|
151
|
-
return false;
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
return true;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* 쿼리스트링 파싱
|
|
162
|
-
*/
|
|
163
|
-
parseQueryString(queryString) {
|
|
164
|
-
const params = {};
|
|
165
|
-
if (!queryString) return params;
|
|
166
|
-
|
|
167
|
-
const pairs = queryString.split('&');
|
|
168
|
-
for (const pair of pairs) {
|
|
169
|
-
try {
|
|
170
|
-
const [rawKey, rawValue] = pair.split('=');
|
|
171
|
-
if (!rawKey) continue;
|
|
172
|
-
|
|
173
|
-
let key, value;
|
|
174
|
-
try {
|
|
175
|
-
key = decodeURIComponent(rawKey);
|
|
176
|
-
value = rawValue ? decodeURIComponent(rawValue) : '';
|
|
177
|
-
} catch (e) {
|
|
178
|
-
this.log('warn', 'Failed to decode URI component:', pair);
|
|
179
|
-
continue;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// 보안 검증
|
|
183
|
-
if (!this.validateParameter(key, value)) {
|
|
184
|
-
this.log('warn', `Parameter rejected by security filter: ${key}`);
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// 값 sanitize
|
|
189
|
-
const sanitizedValue = this.sanitizeParameter(value);
|
|
190
|
-
|
|
191
|
-
// 배열 형태의 파라미터 처리 (예: tags[]=a&tags[]=b)
|
|
192
|
-
if (key.endsWith('[]')) {
|
|
193
|
-
const arrayKey = key.slice(0, -2);
|
|
194
|
-
|
|
195
|
-
// 배열 키도 검증
|
|
196
|
-
if (!this.validateParameter(arrayKey, [])) {
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
if (!params[arrayKey]) params[arrayKey] = [];
|
|
201
|
-
|
|
202
|
-
// 배열 크기 제한
|
|
203
|
-
if (params[arrayKey].length < this.config.maxArraySize) {
|
|
204
|
-
params[arrayKey].push(sanitizedValue);
|
|
205
|
-
} else {
|
|
206
|
-
if (this.config.logSecurityWarnings) {
|
|
207
|
-
console.warn(`Array parameter ${arrayKey} size limit exceeded`);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
} else {
|
|
211
|
-
params[key] = sanitizedValue;
|
|
212
|
-
}
|
|
213
|
-
} catch (error) {
|
|
214
|
-
this.log('error', 'Error parsing query parameter:', pair, error);
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// 전체 파라미터 개수 제한
|
|
219
|
-
const paramCount = Object.keys(params).length;
|
|
220
|
-
if (paramCount > this.config.maxParameterCount) {
|
|
221
|
-
if (this.config.logSecurityWarnings) {
|
|
222
|
-
console.warn(`Too many parameters (${paramCount}). Limiting to first ${this.config.maxParameterCount}.`);
|
|
223
|
-
}
|
|
224
|
-
const limitedParams = {};
|
|
225
|
-
let count = 0;
|
|
226
|
-
for (const [key, value] of Object.entries(params)) {
|
|
227
|
-
if (count >= this.config.maxParameterCount) break;
|
|
228
|
-
limitedParams[key] = value;
|
|
229
|
-
count++;
|
|
230
|
-
}
|
|
231
|
-
return limitedParams;
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
return params;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* 쿼리스트링 생성
|
|
239
|
-
*/
|
|
240
|
-
buildQueryString(params) {
|
|
241
|
-
if (!params || Object.keys(params).length === 0) return '';
|
|
242
|
-
|
|
243
|
-
const pairs = [];
|
|
244
|
-
for (const [key, value] of Object.entries(params)) {
|
|
245
|
-
if (Array.isArray(value)) {
|
|
246
|
-
// 배열 파라미터 처리
|
|
247
|
-
for (const item of value) {
|
|
248
|
-
pairs.push(`${encodeURIComponent(key)}[]=${encodeURIComponent(item)}`);
|
|
249
|
-
}
|
|
250
|
-
} else if (value !== undefined && value !== null) {
|
|
251
|
-
pairs.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);
|
|
252
|
-
}
|
|
253
|
-
}
|
|
254
|
-
return pairs.join('&');
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/**
|
|
258
|
-
* 쿼리 파라미터 변경 감지
|
|
259
|
-
*/
|
|
260
|
-
hasQueryParamsChanged(newParams) {
|
|
261
|
-
if (!this.currentQueryParams && !newParams) return false;
|
|
262
|
-
if (!this.currentQueryParams || !newParams) return true;
|
|
263
|
-
|
|
264
|
-
const oldKeys = Object.keys(this.currentQueryParams);
|
|
265
|
-
const newKeys = Object.keys(newParams);
|
|
266
|
-
|
|
267
|
-
if (oldKeys.length !== newKeys.length) return true;
|
|
268
|
-
|
|
269
|
-
for (const key of oldKeys) {
|
|
270
|
-
if (JSON.stringify(this.currentQueryParams[key]) !== JSON.stringify(newParams[key])) {
|
|
271
|
-
return true;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* 현재 쿼리 파라미터 전체 가져오기
|
|
279
|
-
*/
|
|
280
|
-
getQueryParams() {
|
|
281
|
-
return { ...this.currentQueryParams };
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
/**
|
|
285
|
-
* 특정 쿼리 파라미터 가져오기
|
|
286
|
-
*/
|
|
287
|
-
getQueryParam(key) {
|
|
288
|
-
return this.currentQueryParams ? this.currentQueryParams[key] : undefined;
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
/**
|
|
292
|
-
* 쿼리 파라미터 설정
|
|
293
|
-
*/
|
|
294
|
-
setQueryParams(params, replace = false) {
|
|
295
|
-
if (!params || typeof params !== 'object') {
|
|
296
|
-
console.warn('Invalid parameters object provided to setQueryParams');
|
|
297
|
-
return;
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
const currentParams = replace ? {} : { ...this.currentQueryParams };
|
|
301
|
-
const sanitizedParams = {};
|
|
302
|
-
|
|
303
|
-
// 파라미터 검증 및 sanitize
|
|
304
|
-
for (const [key, value] of Object.entries(params)) {
|
|
305
|
-
// 키와 값 검증
|
|
306
|
-
if (!this.validateParameter(key, value)) {
|
|
307
|
-
console.warn(`Parameter ${key} rejected by security filter`);
|
|
308
|
-
continue;
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
// 값 처리
|
|
312
|
-
if (value !== undefined && value !== null) {
|
|
313
|
-
if (Array.isArray(value)) {
|
|
314
|
-
sanitizedParams[key] = value.map(item => this.sanitizeParameter(item));
|
|
315
|
-
} else {
|
|
316
|
-
sanitizedParams[key] = this.sanitizeParameter(value);
|
|
317
|
-
}
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// 현재 파라미터 업데이트
|
|
322
|
-
Object.assign(currentParams, sanitizedParams);
|
|
323
|
-
|
|
324
|
-
// undefined나 null 값 제거
|
|
325
|
-
for (const [key, value] of Object.entries(currentParams)) {
|
|
326
|
-
if (value === undefined || value === null || value === '') {
|
|
327
|
-
delete currentParams[key];
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// 최대 파라미터 개수 확인
|
|
332
|
-
const paramCount = Object.keys(currentParams).length;
|
|
333
|
-
if (paramCount > this.config.maxParameterCount) {
|
|
334
|
-
if (this.config.logSecurityWarnings) {
|
|
335
|
-
console.warn(`Too many parameters after update (${paramCount}). Some parameters may be dropped.`);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
|
|
339
|
-
this.currentQueryParams = currentParams;
|
|
340
|
-
this.updateURL();
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
/**
|
|
344
|
-
* 쿼리 파라미터 제거
|
|
345
|
-
*/
|
|
346
|
-
removeQueryParams(keys) {
|
|
347
|
-
if (!keys) return;
|
|
348
|
-
|
|
349
|
-
const keysToRemove = Array.isArray(keys) ? keys : [keys];
|
|
350
|
-
for (const key of keysToRemove) {
|
|
351
|
-
delete this.currentQueryParams[key];
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
this.updateURL();
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
/**
|
|
358
|
-
* 쿼리 파라미터 초기화
|
|
359
|
-
*/
|
|
360
|
-
clearQueryParams() {
|
|
361
|
-
this.currentQueryParams = {};
|
|
362
|
-
this.updateURL();
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
/**
|
|
366
|
-
* 현재 쿼리 파라미터 설정 (라우터에서 호출)
|
|
367
|
-
*/
|
|
368
|
-
setCurrentQueryParams(params) {
|
|
369
|
-
this.currentQueryParams = params || {};
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
/**
|
|
373
|
-
* URL 업데이트 (라우터의 updateURL 메소드 호출)
|
|
374
|
-
*/
|
|
375
|
-
updateURL() {
|
|
376
|
-
if (this.router && typeof this.router.updateURL === 'function') {
|
|
377
|
-
const route = this.router.currentHash || 'home';
|
|
378
|
-
this.router.updateURL(route, this.currentQueryParams);
|
|
379
|
-
}
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
/**
|
|
383
|
-
* 쿼리 파라미터 통계
|
|
384
|
-
*/
|
|
385
|
-
getStats() {
|
|
386
|
-
return {
|
|
387
|
-
currentParams: Object.keys(this.currentQueryParams).length,
|
|
388
|
-
maxAllowed: this.config.maxParameterCount,
|
|
389
|
-
validationEnabled: this.config.enableParameterValidation,
|
|
390
|
-
currentQueryString: this.buildQueryString(this.currentQueryParams)
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
/**
|
|
395
|
-
* 정리 (메모리 누수 방지)
|
|
396
|
-
*/
|
|
397
|
-
destroy() {
|
|
398
|
-
this.currentQueryParams = {};
|
|
399
|
-
this.router = null;
|
|
400
|
-
this.log('debug', 'QueryManager destroyed');
|
|
401
|
-
}
|
|
402
|
-
}
|