@forestadmin/agent 1.79.5 → 1.81.0
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/routes/workflow/workflow-executor-proxy.d.ts +4 -0
- package/dist/routes/workflow/workflow-executor-proxy.js +81 -27
- package/dist/types.d.ts +2 -2
- package/dist/utils/forest-schema/generator-collection.d.ts +2 -0
- package/dist/utils/forest-schema/generator-collection.js +19 -4
- package/package.json +1 -1
|
@@ -6,9 +6,13 @@ import BaseRoute from '../base-route';
|
|
|
6
6
|
export default class WorkflowExecutorProxyRoute extends BaseRoute {
|
|
7
7
|
readonly type = RouteType.PrivateRoute;
|
|
8
8
|
private readonly executorUrl;
|
|
9
|
+
protected readonly requestTimeoutMs: number;
|
|
9
10
|
constructor(services: ForestAdminHttpDriverServices, options: AgentOptionsWithDefaults);
|
|
10
11
|
setupRoutes(router: KoaRouter): void;
|
|
11
12
|
private handleProxy;
|
|
13
|
+
private buildTargetUrl;
|
|
14
|
+
private executorPath;
|
|
15
|
+
private forwardedHeaders;
|
|
12
16
|
private forwardRequest;
|
|
13
17
|
}
|
|
14
18
|
//# sourceMappingURL=workflow-executor-proxy.d.ts.map
|
|
@@ -3,46 +3,96 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const datasource_toolkit_1 = require("@forestadmin/datasource-toolkit");
|
|
6
7
|
const http_1 = require("http");
|
|
7
8
|
const https_1 = require("https");
|
|
8
9
|
const types_1 = require("../../types");
|
|
9
10
|
const base_route_1 = __importDefault(require("../base-route"));
|
|
11
|
+
// Any sub-path under this prefix is forwarded verbatim to the executor root, so a new executor
|
|
12
|
+
// route needs no agent change.
|
|
13
|
+
const AGENT_PREFIX = '/_internal/executor';
|
|
14
|
+
// Never forwarded: hop-by-hop headers, Host (Node derives the executor's from the target URL),
|
|
15
|
+
// and length/encoding the HTTP client recomputes.
|
|
16
|
+
const SKIPPED_HEADERS = new Set([
|
|
17
|
+
'connection',
|
|
18
|
+
'keep-alive',
|
|
19
|
+
'transfer-encoding',
|
|
20
|
+
'upgrade',
|
|
21
|
+
'te',
|
|
22
|
+
'trailer',
|
|
23
|
+
'proxy-authenticate',
|
|
24
|
+
'proxy-authorization',
|
|
25
|
+
'host',
|
|
26
|
+
'content-length',
|
|
27
|
+
]);
|
|
28
|
+
// Substrings that could let the wildcard escape the executor origin (traversal, encoded dots,
|
|
29
|
+
// backslash, null byte). SSRF hygiene — not a namespace allowlist.
|
|
30
|
+
const UNSAFE_PATH_FRAGMENTS = ['..', '%2e', '%2E', '\\', '\0'];
|
|
31
|
+
const REQUEST_TIMEOUT_MS = 120000;
|
|
10
32
|
class WorkflowExecutorProxyRoute extends base_route_1.default {
|
|
11
33
|
constructor(services, options) {
|
|
12
34
|
super(services, options);
|
|
13
35
|
this.type = types_1.RouteType.PrivateRoute;
|
|
14
|
-
//
|
|
36
|
+
// Overridable so tests can exercise the timeout branch without waiting the full default.
|
|
37
|
+
this.requestTimeoutMs = REQUEST_TIMEOUT_MS;
|
|
15
38
|
this.executorUrl = new URL(options.workflowExecutorUrl.replace(/\/+$/, ''));
|
|
16
39
|
}
|
|
17
40
|
setupRoutes(router) {
|
|
18
|
-
router.
|
|
19
|
-
router.post('/_internal/workflow-executions/:runId/trigger', this.handleProxy.bind(this));
|
|
41
|
+
router.all(`${AGENT_PREFIX}/:path(.*)`, this.handleProxy.bind(this));
|
|
20
42
|
}
|
|
21
43
|
async handleProxy(context) {
|
|
22
|
-
const
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
};
|
|
31
|
-
const response = await this.forwardRequest(context.method, targetUrl, context.request.body, forwardedHeaders);
|
|
44
|
+
const targetUrl = this.buildTargetUrl(context);
|
|
45
|
+
const response = await this.forwardRequest({
|
|
46
|
+
method: context.method,
|
|
47
|
+
url: targetUrl,
|
|
48
|
+
// Raw body forwarded verbatim (set by @koa/bodyparser); undefined for GET.
|
|
49
|
+
body: context.method === 'GET' ? undefined : context.request.rawBody,
|
|
50
|
+
headers: this.forwardedHeaders(context),
|
|
51
|
+
});
|
|
32
52
|
context.response.status = response.status;
|
|
53
|
+
// Forward every executor response header (minus hop-by-hop) so new executor headers never
|
|
54
|
+
// require an agent change — the agent stays a transparent proxy.
|
|
55
|
+
for (const [name, value] of Object.entries(response.headers)) {
|
|
56
|
+
if (value !== undefined && !SKIPPED_HEADERS.has(name.toLowerCase())) {
|
|
57
|
+
context.response.set(name, value);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
33
60
|
context.response.body = response.body;
|
|
34
61
|
}
|
|
35
|
-
|
|
62
|
+
buildTargetUrl(context) {
|
|
63
|
+
const path = this.executorPath(String(context.params.path ?? ''));
|
|
64
|
+
const qs = context.querystring ? `?${context.querystring}` : '';
|
|
65
|
+
const targetUrl = new URL(`${path}${qs}`, this.executorUrl);
|
|
66
|
+
// Authoritative SSRF check: URL parsing strips control chars the string guard can't see
|
|
67
|
+
// (e.g. a decoded `\t//host` collapses to `//host`), so confirm we never left the executor.
|
|
68
|
+
if (targetUrl.origin !== this.executorUrl.origin) {
|
|
69
|
+
throw new datasource_toolkit_1.NotFoundError('Invalid workflow executor path');
|
|
70
|
+
}
|
|
71
|
+
return targetUrl;
|
|
72
|
+
}
|
|
73
|
+
// First-pass rejection of escape attempts; the authoritative origin check is in buildTargetUrl.
|
|
74
|
+
executorPath(wildcard) {
|
|
75
|
+
const unsafe = wildcard === '' ||
|
|
76
|
+
wildcard.startsWith('/') ||
|
|
77
|
+
UNSAFE_PATH_FRAGMENTS.some(fragment => wildcard.includes(fragment));
|
|
78
|
+
if (unsafe)
|
|
79
|
+
throw new datasource_toolkit_1.NotFoundError('Invalid workflow executor path');
|
|
80
|
+
return `/${wildcard}`;
|
|
81
|
+
}
|
|
82
|
+
forwardedHeaders(context) {
|
|
83
|
+
const headers = {};
|
|
84
|
+
for (const [name, value] of Object.entries(context.request.headers)) {
|
|
85
|
+
if (value !== undefined && !SKIPPED_HEADERS.has(name.toLowerCase())) {
|
|
86
|
+
headers[name] = value;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return headers;
|
|
90
|
+
}
|
|
91
|
+
forwardRequest(request) {
|
|
92
|
+
const { method, url, body, headers } = request;
|
|
36
93
|
const requestFn = url.protocol === 'https:' ? https_1.request : http_1.request;
|
|
37
|
-
const headers = { 'Content-Type': 'application/json' };
|
|
38
|
-
// Forward the caller's auth so the executor's JWT middleware can validate it.
|
|
39
|
-
// Agent and executor share the same FOREST_AUTH_SECRET so the token is valid on both.
|
|
40
|
-
if (forwardedHeaders.authorization)
|
|
41
|
-
headers.Authorization = forwardedHeaders.authorization;
|
|
42
|
-
if (forwardedHeaders.cookie)
|
|
43
|
-
headers.Cookie = forwardedHeaders.cookie;
|
|
44
94
|
return new Promise((resolve, reject) => {
|
|
45
|
-
const req = requestFn(url, { method, headers }, res => {
|
|
95
|
+
const req = requestFn(url, { method, headers, timeout: this.requestTimeoutMs }, res => {
|
|
46
96
|
const chunks = [];
|
|
47
97
|
res.on('data', chunk => chunks.push(chunk));
|
|
48
98
|
res.on('end', () => {
|
|
@@ -54,17 +104,21 @@ class WorkflowExecutorProxyRoute extends base_route_1.default {
|
|
|
54
104
|
catch {
|
|
55
105
|
parsed = raw;
|
|
56
106
|
}
|
|
57
|
-
resolve({
|
|
107
|
+
resolve({
|
|
108
|
+
status: res.statusCode ?? types_1.HttpCode.InternalServerError,
|
|
109
|
+
body: parsed,
|
|
110
|
+
headers: res.headers,
|
|
111
|
+
});
|
|
58
112
|
});
|
|
59
113
|
res.on('error', reject);
|
|
60
114
|
});
|
|
61
115
|
req.on('error', reject);
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
116
|
+
req.on('timeout', () => req.destroy(new Error('Workflow executor request timed out')));
|
|
117
|
+
if (body !== undefined && method !== 'GET')
|
|
118
|
+
req.write(body);
|
|
65
119
|
req.end();
|
|
66
120
|
});
|
|
67
121
|
}
|
|
68
122
|
}
|
|
69
123
|
exports.default = WorkflowExecutorProxyRoute;
|
|
70
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
124
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoid29ya2Zsb3ctZXhlY3V0b3ItcHJveHkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvcm91dGVzL3dvcmtmbG93L3dvcmtmbG93LWV4ZWN1dG9yLXByb3h5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7Ozs7O0FBTUEsd0VBQWdFO0FBQ2hFLCtCQUE4QztBQUM5QyxpQ0FBZ0Q7QUFFaEQsdUNBQWtEO0FBQ2xELCtEQUFzQztBQUV0QywrRkFBK0Y7QUFDL0YsK0JBQStCO0FBQy9CLE1BQU0sWUFBWSxHQUFHLHFCQUFxQixDQUFDO0FBQzNDLCtGQUErRjtBQUMvRixrREFBa0Q7QUFDbEQsTUFBTSxlQUFlLEdBQUcsSUFBSSxHQUFHLENBQUM7SUFDOUIsWUFBWTtJQUNaLFlBQVk7SUFDWixtQkFBbUI7SUFDbkIsU0FBUztJQUNULElBQUk7SUFDSixTQUFTO0lBQ1Qsb0JBQW9CO0lBQ3BCLHFCQUFxQjtJQUNyQixNQUFNO0lBQ04sZ0JBQWdCO0NBQ2pCLENBQUMsQ0FBQztBQUNILDhGQUE4RjtBQUM5RixtRUFBbUU7QUFDbkUsTUFBTSxxQkFBcUIsR0FBRyxDQUFDLElBQUksRUFBRSxLQUFLLEVBQUUsS0FBSyxFQUFFLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztBQUMvRCxNQUFNLGtCQUFrQixHQUFHLE1BQU8sQ0FBQztBQUVuQyxNQUFxQiwwQkFBMkIsU0FBUSxvQkFBUztJQU0vRCxZQUFZLFFBQXVDLEVBQUUsT0FBaUM7UUFDcEYsS0FBSyxDQUFDLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQztRQU5sQixTQUFJLEdBQUcsaUJBQVMsQ0FBQyxZQUFZLENBQUM7UUFFdkMseUZBQXlGO1FBQ3RFLHFCQUFnQixHQUFXLGtCQUFrQixDQUFDO1FBSS9ELElBQUksQ0FBQyxXQUFXLEdBQUcsSUFBSSxHQUFHLENBQUMsT0FBTyxDQUFDLG1CQUFtQixDQUFDLE9BQU8sQ0FBQyxNQUFNLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQztJ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
|
package/dist/types.d.ts
CHANGED
|
@@ -46,8 +46,8 @@ export type AgentOptions = {
|
|
|
46
46
|
useUnsafeActionEndpoint?: boolean;
|
|
47
47
|
/**
|
|
48
48
|
* Base URL of the workflow executor to proxy requests to.
|
|
49
|
-
* When set, the agent
|
|
50
|
-
*
|
|
49
|
+
* When set, the agent forwards `/_internal/executor/*` to the executor verbatim,
|
|
50
|
+
* benefiting from the agent's authentication layer.
|
|
51
51
|
* @example 'http://localhost:4001'
|
|
52
52
|
*/
|
|
53
53
|
workflowExecutorUrl?: string | null;
|
|
@@ -3,10 +3,12 @@ import type { Collection } from '@forestadmin/datasource-toolkit';
|
|
|
3
3
|
import type { ForestServerCollection } from '@forestadmin/forestadmin-client';
|
|
4
4
|
export default class SchemaGeneratorCollection {
|
|
5
5
|
private readonly schemaGeneratorActions;
|
|
6
|
+
private readonly useUnsafeActionEndpoint;
|
|
6
7
|
constructor(options: AgentOptionsWithDefaults);
|
|
7
8
|
/** Build forest-server schema for a collection */
|
|
8
9
|
buildSchema(collection: Collection): Promise<ForestServerCollection>;
|
|
9
10
|
private buildActions;
|
|
11
|
+
private static assertNoActionSlugCollision;
|
|
10
12
|
private static buildFields;
|
|
11
13
|
private static buildSegments;
|
|
12
14
|
}
|
|
@@ -10,6 +10,7 @@ const generator_segments_1 = __importDefault(require("./generator-segments"));
|
|
|
10
10
|
class SchemaGeneratorCollection {
|
|
11
11
|
constructor(options) {
|
|
12
12
|
this.schemaGeneratorActions = new generator_actions_1.default(options);
|
|
13
|
+
this.useUnsafeActionEndpoint = options.useUnsafeActionEndpoint;
|
|
13
14
|
}
|
|
14
15
|
/** Build forest-server schema for a collection */
|
|
15
16
|
async buildSchema(collection) {
|
|
@@ -28,9 +29,23 @@ class SchemaGeneratorCollection {
|
|
|
28
29
|
};
|
|
29
30
|
}
|
|
30
31
|
buildActions(collection) {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
.
|
|
32
|
+
const names = Object.keys(collection.schema.actions).sort();
|
|
33
|
+
if (this.useUnsafeActionEndpoint) {
|
|
34
|
+
SchemaGeneratorCollection.assertNoActionSlugCollision(collection.name, names);
|
|
35
|
+
}
|
|
36
|
+
return Promise.all(names.map(name => this.schemaGeneratorActions.buildSchema(collection, name)));
|
|
37
|
+
}
|
|
38
|
+
static assertNoActionSlugCollision(collectionName, names) {
|
|
39
|
+
const nameBySlug = new Map();
|
|
40
|
+
names.forEach(name => {
|
|
41
|
+
const slug = generator_actions_1.default.getActionSlug(name);
|
|
42
|
+
const existing = nameBySlug.get(slug);
|
|
43
|
+
if (existing) {
|
|
44
|
+
throw new Error(`Actions "${existing}" and "${name}" on collection "${collectionName}" resolve to the ` +
|
|
45
|
+
`same endpoint slug "${slug}". Rename one of them or disable useUnsafeActionEndpoint.`);
|
|
46
|
+
}
|
|
47
|
+
nameBySlug.set(slug, name);
|
|
48
|
+
});
|
|
34
49
|
}
|
|
35
50
|
static buildFields(collection) {
|
|
36
51
|
// Do not export foreign keys as those will be edited using the many to one relationship.
|
|
@@ -48,4 +63,4 @@ class SchemaGeneratorCollection {
|
|
|
48
63
|
}
|
|
49
64
|
}
|
|
50
65
|
exports.default = SchemaGeneratorCollection;
|
|
51
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
66
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZ2VuZXJhdG9yLWNvbGxlY3Rpb24uanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvdXRpbHMvZm9yZXN0LXNjaGVtYS9nZW5lcmF0b3ItY29sbGVjdGlvbi50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7OztBQVNBLHdFQUE4RDtBQUU5RCw0RUFBeUQ7QUFDekQsMEVBQXVEO0FBQ3ZELDhFQUEyRDtBQUUzRCxNQUFxQix5QkFBeUI7SUFJNUMsWUFBWSxPQUFpQztRQUMzQyxJQUFJLENBQUMsc0JBQXNCLEdBQUcsSUFBSSwyQkFBc0IsQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUNsRSxJQUFJLENBQUMsdUJBQXVCLEdBQUcsT0FBTyxDQUFDLHVCQUF1QixDQUFDO0lBQ2pFLENBQUM7SUFFRCxrREFBa0Q7SUFDbEQsS0FBSyxDQUFDLFdBQVcsQ0FBQyxVQUFzQjtRQUN0QyxPQUFPO1lBQ0wsT0FBTyxFQUFFLE1BQU0sSUFBSSxDQUFDLFlBQVksQ0FBQyxVQUFVLENBQUM7WUFDNUMsTUFBTSxFQUFFLHlCQUF5QixDQUFDLFdBQVcsQ0FBQyxVQUFVLENBQUM7WUFDekQsSUFBSSxFQUFFLElBQUk7WUFDVixXQUFXLEVBQUUsSUFBSTtZQUNqQixVQUFVLEVBQUUsTUFBTSxDQUFDLE1BQU0sQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLE1BQU0sQ0FBQyxDQUFDLEtBQUssQ0FDdkQsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsSUFBSSxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsVUFBVSxDQUNyRDtZQUNELFlBQVksRUFBRSxVQUFVLENBQUMsTUFBTSxDQUFDLFVBQVU7WUFDMUMsU0FBUyxFQUFFLEtBQUs7WUFDaEIsSUFBSSxFQUFFLFVBQVUsQ0FBQyxJQUFJO1lBQ3JCLG9CQUFvQixFQUFFLEtBQUs7WUFDM0IsY0FBYyxFQUFFLE1BQU07WUFDdEIsUUFBUSxFQUFFLHlCQUF5QixDQUFDLGFBQWEsQ0FBQyxVQUFVLENBQUM7U0FDOUQsQ0FBQztJQUNKLENBQUM7SUFFTyxZQUFZLENBQUMsVUFBc0I7UUFDekMsTUFBTSxLQUFLLEdBQUcsTUFBTSxDQUFDLElBQUksQ0FBQyxVQUFVLENBQUMsTUFBTSxDQUFDLE9BQU8sQ0FBQyxDQUFDLElBQUksRUFBRSxDQUFDO1FBRTVELElBQUksSUFBSSxDQUFDLHVCQUF1QixFQUFFLENBQUM7WUFDakMseUJBQXlCLENBQUMsMkJBQTJCLENBQUMsVUFBVSxDQUFDLElBQUksRUFBRSxLQUFLLENBQUMsQ0FBQztRQUNoRixDQUFDO1FBRUQsT0FBTyxPQUFPLENBQUMsR0FBRyxDQUNoQixLQUFLLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsSUFBSSxDQUFDLHNCQUFzQixDQUFDLFdBQVcsQ0FBQyxVQUFVLEVBQUUsSUFBSSxDQUFDLENBQUMsQ0FDN0UsQ0FBQztJQUNKLENBQUM7SUFFTyxNQUFNLENBQUMsMkJBQTJCLENBQUMsY0FBc0IsRUFBRSxLQUFlO1FBQ2hGLE1BQU0sVUFBVSxHQUFHLElBQUksR0FBRyxFQUFrQixDQUFDO1FBRTdDLEtBQUssQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLEVBQUU7WUFDbkIsTUFBTSxJQUFJLEdBQUcsMkJBQXNCLENBQUMsYUFBYSxDQUFDLElBQUksQ0FBQyxDQUFDO1lBQ3hELE1BQU0sUUFBUSxHQUFHLFVBQVUsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7WUFFdEMsSUFBSSxRQUFRLEVBQUUsQ0FBQztnQkFDYixNQUFNLElBQUksS0FBSyxDQUNiLFlBQVksUUFBUSxVQUFVLElBQUksb0JBQW9CLGNBQWMsbUJBQW1CO29CQUNyRix1QkFBdUIsSUFBSSwyREFBMkQsQ0FDekYsQ0FBQztZQUNKLENBQUM7WUFFRCxVQUFVLENBQUMsR0FBRyxDQUFDLElBQUksRUFBRSxJQUFJLENBQUMsQ0FBQztRQUM3QixDQUFDLENBQUMsQ0FBQztJQUNMLENBQUM7SUFFTyxNQUFNLENBQUMsV0FBVyxDQUFDLFVBQXNCO1FBQy9DLHlGQUF5RjtRQUN6Riw4RkFBOEY7UUFDOUYsT0FBTyxNQUFNLENBQUMsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDO2FBQ3pDLE1BQU0sQ0FDTCxJQUFJLENBQUMsRUFBRSxDQUNMLGdDQUFXLENBQUMsWUFBWSxDQUFDLFVBQVUsQ0FBQyxNQUFNLEVBQUUsSUFBSSxDQUFDO1lBQ2pELENBQUMsZ0NBQVcsQ0FBQyxZQUFZLENBQUMsVUFBVSxDQUFDLE1BQU0sRUFBRSxJQUFJLENBQUMsQ0FDckQ7YUFDQSxJQUFJLEVBQUU7YUFDTixHQUFHLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQywwQkFBcUIsQ0FBQyxXQUFXLENBQUMsVUFBVSxFQUFFLElBQUksQ0FBQyxDQUFDLENBQUM7SUFDdEUsQ0FBQztJQUVPLE1BQU0sQ0FBQyxhQUFhLENBQUMsVUFBc0I7UUFDakQsT0FBTyxVQUFVLENBQUMsTUFBTSxDQUFDLFFBQVE7YUFDOUIsSUFBSSxFQUFFO2FBQ04sR0FBRyxDQUFDLElBQUksQ0FBQyxFQUFFLENBQUMsNEJBQXVCLENBQUMsV0FBVyxDQUFDLFVBQVUsRUFBRSxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQ3hFLENBQUM7Q0FDRjtBQTVFRCw0Q0E0RUMifQ==
|