-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathresolver.ts
More file actions
171 lines (151 loc) · 5.48 KB
/
resolver.ts
File metadata and controls
171 lines (151 loc) · 5.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
import type { Token, ServiceDefinition, IContainer } from "../types/index.js";
import { CircularDependencyError, UnregisteredServiceError, ResolutionError } from "../errors/index.js";
/**
* Maximum depth for dependency resolution to prevent stack overflow attacks
* This prevents DoS attacks via deeply nested legitimate dependency chains
*/
const MAX_RESOLUTION_DEPTH = 100;
/**
* Resolver handles dependency resolution with circular dependency detection
*/
export class Resolver {
/**
* Resolve a service with circular dependency detection
*/
resolve<T>(
token: Token<T>,
definition: ServiceDefinition<T>,
container: IContainer,
_instanceCache: Map<Token, any>,
stack: Set<Token> = new Set(),
): T {
// Check for circular dependencies
if (stack.has(token)) {
throw new CircularDependencyError([...stack, token]);
}
// Check for excessive dependency depth (DoS prevention)
if (stack.size >= MAX_RESOLUTION_DEPTH) {
throw new ResolutionError(
token,
`Maximum dependency depth (${MAX_RESOLUTION_DEPTH}) exceeded. This may indicate a misconfigured dependency chain or an attack attempt.`,
);
}
try {
stack.add(token);
// Handle factory functions
if (definition.isFactory) {
if (definition.isAsync) {
throw new ResolutionError(token, "Async factory must be resolved using resolveAsync()");
}
// Create a wrapper container that passes the stack
const wrappedContainer = this.wrapContainerWithStack(container, stack);
return (definition.target as Function)(wrappedContainer) as T;
}
// Handle value (already instantiated)
if (typeof definition.target !== "function") {
return definition.target as T;
}
// Resolve constructor dependencies
const Constructor = definition.target as new (...args: any[]) => T;
const dependencies = definition.dependencies.map((depToken) => {
try {
// Pass the stack to the container for recursive resolution
return (container as any).resolve(depToken, stack);
} catch (error) {
if (error instanceof UnregisteredServiceError) {
throw new ResolutionError(token, `Missing dependency: ${this.tokenToString(depToken)}`, error as Error);
}
throw error;
}
});
// Create instance
return new Constructor(...dependencies);
} finally {
stack.delete(token);
}
}
/**
* Resolve a service asynchronously
*/
async resolveAsync<T>(
token: Token<T>,
definition: ServiceDefinition<T>,
container: IContainer,
_instanceCache: Map<Token, any>,
stack: Set<Token> = new Set(),
): Promise<T> {
// Check for circular dependencies
if (stack.has(token)) {
throw new CircularDependencyError([...stack, token]);
}
// Check for excessive dependency depth (DoS prevention)
if (stack.size >= MAX_RESOLUTION_DEPTH) {
throw new ResolutionError(
token,
`Maximum dependency depth (${MAX_RESOLUTION_DEPTH}) exceeded. This may indicate a misconfigured dependency chain or an attack attempt.`,
);
}
try {
stack.add(token);
// Handle async factory functions
if (definition.isAsync) {
const wrappedContainer = this.wrapContainerWithStack(container, stack);
return (await (definition.target as Function)(wrappedContainer)) as T;
}
// Handle regular factory functions
if (definition.isFactory) {
const wrappedContainer = this.wrapContainerWithStack(container, stack);
return (definition.target as Function)(wrappedContainer) as T;
}
// Handle value (already instantiated)
if (typeof definition.target !== "function") {
return definition.target as T;
}
// Resolve constructor dependencies
const Constructor = definition.target as new (...args: any[]) => T;
const dependencyPromises = definition.dependencies.map(async (depToken) => {
try {
// Pass the stack to the container for recursive resolution
return await (container as any).resolveAsync(depToken, stack);
} catch (error) {
if (error instanceof UnregisteredServiceError) {
throw new ResolutionError(token, `Missing dependency: ${this.tokenToString(depToken)}`, error as Error);
}
throw error;
}
});
const dependencies = await Promise.all(dependencyPromises);
// Create instance
return new Constructor(...dependencies);
} finally {
stack.delete(token);
}
}
/**
* Convert token to string for error messages
*/
private tokenToString(token: Token): string {
if (typeof token === "function") {
return token.name || "AnonymousClass";
}
return String(token);
}
/**
* Wrap container to pass stack through factory function calls
*/
private wrapContainerWithStack(container: IContainer, stack: Set<Token>): IContainer {
return {
...container,
resolve: <T>(token: Token<T>) => (container as any).resolve(token, stack),
resolveAsync: <T>(token: Token<T>) => (container as any).resolveAsync(token, stack),
resolveAll: <T>(token: Token<T>) => (container as any).resolveAll(token, stack),
};
}
/**
* Clear resolution stack (useful for testing)
*/
clear(): void {
// This method exists for backward compatibility with tests
// The stack is now managed per-resolution, so there's nothing to clear
}
}