tina4-nodejs 3.13.137 → 3.13.138
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/CLAUDE.md +33 -3
- package/COMMERCIAL-LICENSE.md +8 -0
- package/LICENSE +373 -0
- package/NOTICE +11 -0
- package/README.md +24 -2
- package/package.json +44 -6
- package/packages/cli/dist/bin.js +19466 -18048
- package/packages/cli/src/bin.ts +8 -0
- package/packages/cli/src/commands/build.ts +8 -0
- package/packages/cli/src/commands/generate.ts +8 -0
- package/packages/cli/src/commands/init.ts +8 -0
- package/packages/cli/src/commands/lint.ts +8 -0
- package/packages/cli/src/commands/migrate.ts +8 -0
- package/packages/cli/src/commands/migrateCreate.ts +8 -0
- package/packages/cli/src/commands/migrateRollback.ts +8 -0
- package/packages/cli/src/commands/migrateStatus.ts +8 -0
- package/packages/cli/src/commands/queue.ts +8 -0
- package/packages/cli/src/commands/routes.ts +8 -0
- package/packages/cli/src/commands/seed.ts +8 -0
- package/packages/cli/src/commands/serve.ts +11 -0
- package/packages/cli/src/commands/test.ts +8 -0
- package/packages/core/dist/index.js +19458 -18043
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/login/post.ts +8 -0
- package/packages/core/gallery/auth/src/routes/api/gallery/auth/verify/get.ts +8 -0
- package/packages/core/gallery/auth/src/routes/gallery/auth/get.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/get.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/notes/post.ts +8 -0
- package/packages/core/gallery/database/src/routes/api/gallery/db/tables/get.ts +8 -0
- package/packages/core/gallery/error-overlay/src/routes/api/gallery/crash/get.ts +8 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/get.ts +8 -0
- package/packages/core/gallery/orm/src/routes/api/gallery/products/post.ts +8 -0
- package/packages/core/gallery/queue/src/lib/queueDb.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/consume/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/fail/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/produce/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/retry/post.ts +8 -0
- package/packages/core/gallery/queue/src/routes/api/gallery/queue/status/get.ts +8 -0
- package/packages/core/gallery/queue/src/routes/gallery/queue/get.ts +8 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/get.ts +8 -0
- package/packages/core/gallery/rest-api/src/routes/api/gallery/hello/post.ts +8 -0
- package/packages/core/gallery/templates/src/routes/gallery/page/get.ts +8 -0
- package/packages/core/gallery/templates/src/templates/gallery_page.twig +8 -0
- package/packages/core/public/THIRD-PARTY-NOTICES.md +55 -0
- package/packages/core/public/bundled-component-provenance.json +48 -0
- package/packages/core/public/js/tina4js.min.js +13 -3
- package/packages/core/public/licenses/swagger-ui-3.10.0-original-notice.txt +11 -0
- package/packages/core/public/licenses/swagger-ui-5.17.14-NOTICE.txt +2 -0
- package/packages/core/public/licenses/swagger-ui-Apache-2.0.txt +202 -0
- package/packages/core/public/licenses/tina4-css-MIT.txt +21 -0
- package/packages/core/public/licenses/tina4-dev-admin-components.json +240 -0
- package/packages/core/public/licenses/tina4-dev-admin-third-party.txt +919 -0
- package/packages/core/public/licenses/tina4-js-MIT.txt +20 -0
- package/packages/core/src/__feedback/widget.js +8 -0
- package/packages/core/src/_missing.ts +8 -0
- package/packages/core/src/ai.ts +8 -0
- package/packages/core/src/aiClient.ts +8 -0
- package/packages/core/src/api.ts +15 -3
- package/packages/core/src/auth.ts +21 -8
- package/packages/core/src/authGate.ts +8 -0
- package/packages/core/src/background.ts +8 -0
- package/packages/core/src/cache.ts +43 -4
- package/packages/core/src/constants.ts +8 -0
- package/packages/core/src/container.ts +8 -0
- package/packages/core/src/context/chunker.ts +8 -0
- package/packages/core/src/context/index.ts +8 -0
- package/packages/core/src/devAdmin.ts +185 -59
- package/packages/core/src/devMailbox.ts +8 -0
- package/packages/core/src/dispatchPipeline.ts +8 -0
- package/packages/core/src/docs.ts +8 -0
- package/packages/core/src/docsAutoDiscovery.ts +8 -0
- package/packages/core/src/docsParser.ts +8 -0
- package/packages/core/src/docsScanner.ts +44 -0
- package/packages/core/src/docsSignatures.ts +8 -0
- package/packages/core/src/dotenv.ts +8 -0
- package/packages/core/src/env.ts +8 -0
- package/packages/core/src/errorOverlay.ts +8 -0
- package/packages/core/src/events.ts +8 -0
- package/packages/core/src/fakeData.ts +8 -0
- package/packages/core/src/feedback.ts +8 -0
- package/packages/core/src/graphql.ts +16 -5
- package/packages/core/src/health.ts +8 -0
- package/packages/core/src/htmlElement.ts +8 -0
- package/packages/core/src/i18n.ts +8 -0
- package/packages/core/src/index.ts +8 -0
- package/packages/core/src/job.ts +8 -0
- package/packages/core/src/logger.ts +8 -0
- package/packages/core/src/mcp.test.ts +8 -0
- package/packages/core/src/mcp.ts +8 -0
- package/packages/core/src/messenger.ts +143 -86
- package/packages/core/src/metrics.ts +13 -0
- package/packages/core/src/middleware.ts +1178 -1096
- package/packages/core/src/mqtt.ts +8 -0
- package/packages/core/src/mqttMessage.ts +8 -0
- package/packages/core/src/optionalPackage.ts +48 -0
- package/packages/core/src/plan.ts +8 -0
- package/packages/core/src/portTakeover.ts +8 -0
- package/packages/core/src/projectIndex.ts +8 -0
- package/packages/core/src/projectIndexExtractors.ts +8 -0
- package/packages/core/src/projectIndexStorage.ts +8 -0
- package/packages/core/src/push.ts +8 -0
- package/packages/core/src/queue.ts +8 -0
- package/packages/core/src/queueBackends/kafkaBackend.ts +8 -0
- package/packages/core/src/queueBackends/liteBackend.ts +8 -0
- package/packages/core/src/queueBackends/mongoBackend.ts +21 -8
- package/packages/core/src/queueBackends/rabbitmqBackend.ts +8 -0
- package/packages/core/src/rateLimiter.ts +8 -0
- package/packages/core/src/request.ts +59 -10
- package/packages/core/src/response.ts +79 -5
- package/packages/core/src/routeDiscovery.ts +8 -0
- package/packages/core/src/router.ts +8 -0
- package/packages/core/src/server.ts +73 -24
- package/packages/core/src/service.ts +8 -0
- package/packages/core/src/session.ts +8 -0
- package/packages/core/src/sessionHandlers/childError.ts +8 -0
- package/packages/core/src/sessionHandlers/databaseHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/memcachedHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/mongoClient.ts +8 -0
- package/packages/core/src/sessionHandlers/mongoHandler.ts +8 -0
- package/packages/core/src/sessionHandlers/respClient.ts +8 -0
- package/packages/core/src/sessionHandlers/sqlClient.ts +8 -0
- package/packages/core/src/sessionHandlers/syncBridge.ts +8 -0
- package/packages/core/src/sessionHandlers/syncSocket.ts +8 -0
- package/packages/core/src/sessionHandlers/valkeyHandler.ts +8 -0
- package/packages/core/src/sso.ts +8 -0
- package/packages/core/src/static.ts +8 -0
- package/packages/core/src/test.ts +8 -0
- package/packages/core/src/testClient.ts +8 -0
- package/packages/core/src/testing.ts +8 -0
- package/packages/core/src/transport.ts +369 -0
- package/packages/core/src/trustedProxy.ts +8 -0
- package/packages/core/src/types.ts +13 -0
- package/packages/core/src/validator.ts +8 -0
- package/packages/core/src/version.ts +8 -0
- package/packages/core/src/websocket.ts +8 -0
- package/packages/core/src/websocketBackplane.ts +49 -32
- package/packages/core/src/websocketConnection.ts +8 -0
- package/packages/core/src/wsdl.ts +197 -115
- package/packages/core/templates/errors/302.twig +8 -0
- package/packages/core/templates/errors/401.twig +8 -0
- package/packages/core/templates/errors/403.twig +8 -0
- package/packages/core/templates/errors/404.twig +8 -0
- package/packages/core/templates/errors/500.twig +8 -0
- package/packages/core/templates/errors/502.twig +8 -0
- package/packages/core/templates/errors/503.twig +8 -0
- package/packages/core/templates/errors/base.twig +8 -0
- package/packages/frond/dist/index.js +151 -17
- package/packages/frond/src/engine.ts +177 -17
- package/packages/frond/src/index.ts +8 -0
- package/packages/orm/dist/index.js +8125 -6708
- package/packages/orm/src/adapters/firebird.ts +50 -3
- package/packages/orm/src/adapters/mongodb.ts +8 -0
- package/packages/orm/src/adapters/mssql.ts +29 -7
- package/packages/orm/src/adapters/mysql.ts +17 -1
- package/packages/orm/src/adapters/odbc.ts +11 -0
- package/packages/orm/src/adapters/pg-types.d.ts +8 -0
- package/packages/orm/src/adapters/postgres.ts +34 -8
- package/packages/orm/src/adapters/sqlDialect.ts +57 -0
- package/packages/orm/src/adapters/sqlite.ts +26 -0
- package/packages/orm/src/autoCrud.ts +78 -36
- package/packages/orm/src/baseModel.ts +61 -29
- package/packages/orm/src/cachedDatabase.ts +67 -1
- package/packages/orm/src/connectTimeout.ts +8 -0
- package/packages/orm/src/database.ts +649 -401
- package/packages/orm/src/databaseResult.ts +8 -0
- package/packages/orm/src/databaseUrl.ts +8 -0
- package/packages/orm/src/docstore.ts +23 -1
- package/packages/orm/src/fakeData.ts +8 -0
- package/packages/orm/src/graph/adapters/arango.ts +8 -0
- package/packages/orm/src/graph/adapters/bolt.ts +8 -0
- package/packages/orm/src/graph/adapters/ultipa.ts +8 -0
- package/packages/orm/src/graph/connectTimeout.ts +8 -0
- package/packages/orm/src/graph/errors.ts +8 -0
- package/packages/orm/src/graph/graphAdapter.ts +8 -0
- package/packages/orm/src/graph/graphDatabase.ts +8 -0
- package/packages/orm/src/graph/graphUrl.ts +10 -1
- package/packages/orm/src/graph/shapes.ts +8 -0
- package/packages/orm/src/index.ts +147 -139
- package/packages/orm/src/migration.ts +8 -0
- package/packages/orm/src/model.ts +8 -0
- package/packages/orm/src/modelCollection.ts +8 -0
- package/packages/orm/src/point.ts +8 -0
- package/packages/orm/src/query.ts +149 -33
- package/packages/orm/src/queryBuilder.ts +8 -0
- package/packages/orm/src/realtime/index.ts +8 -0
- package/packages/orm/src/realtime/models/attachment.ts +8 -0
- package/packages/orm/src/realtime/models/channel.ts +8 -0
- package/packages/orm/src/realtime/models/channelMember.ts +8 -0
- package/packages/orm/src/realtime/models/message.ts +8 -0
- package/packages/orm/src/realtime/models/workspace.ts +8 -0
- package/packages/orm/src/realtime/realtime.ts +8 -0
- package/packages/orm/src/realtime/storage.ts +43 -12
- package/packages/orm/src/seeder.ts +8 -0
- package/packages/orm/src/seederTable.ts +8 -0
- package/packages/orm/src/seederTypes.ts +8 -0
- package/packages/orm/src/sqlTranslator.ts +185 -8
- package/packages/orm/src/types.ts +15 -0
- package/packages/orm/src/validation.ts +8 -0
- package/packages/swagger/src/generator.ts +8 -0
- package/packages/swagger/src/index.ts +8 -0
- package/packages/swagger/src/ui.ts +8 -0
- package/types/core/src/background.d.ts +0 -16
- package/types/core/src/cache.d.ts +6 -0
- package/types/core/src/devAdmin.d.ts +18 -12
- package/types/core/src/docs.d.ts +0 -17
- package/types/core/src/errorOverlay.d.ts +0 -24
- package/types/core/src/events.d.ts +0 -12
- package/types/core/src/feedback.d.ts +0 -23
- package/types/core/src/graphql.d.ts +0 -21
- package/types/core/src/htmlElement.d.ts +0 -15
- package/types/core/src/messenger.d.ts +12 -1
- package/types/core/src/metrics.d.ts +2 -0
- package/types/core/src/middleware.d.ts +45 -1
- package/types/core/src/mqtt.d.ts +0 -33
- package/types/core/src/optionalPackage.d.ts +25 -0
- package/types/core/src/plan.d.ts +0 -10
- package/types/core/src/projectIndex.d.ts +0 -10
- package/types/core/src/queueBackends/mongoBackend.d.ts +2 -0
- package/types/core/src/request.d.ts +12 -0
- package/types/core/src/response.d.ts +13 -0
- package/types/core/src/server.d.ts +19 -0
- package/types/core/src/sessionHandlers/childError.d.ts +0 -15
- package/types/core/src/transport.d.ts +28 -0
- package/types/core/src/trustedProxy.d.ts +0 -16
- package/types/core/src/types.d.ts +5 -0
- package/types/core/src/websocketBackplane.d.ts +1 -0
- package/types/core/src/wsdl.d.ts +8 -22
- package/types/frond/src/engine.d.ts +7 -1
- package/types/orm/src/adapters/firebird.d.ts +12 -0
- package/types/orm/src/adapters/mssql.d.ts +11 -0
- package/types/orm/src/adapters/mysql.d.ts +2 -0
- package/types/orm/src/adapters/postgres.d.ts +11 -0
- package/types/orm/src/adapters/sqlDialect.d.ts +25 -0
- package/types/orm/src/adapters/sqlite.d.ts +7 -0
- package/types/orm/src/baseModel.d.ts +5 -0
- package/types/orm/src/cachedDatabase.d.ts +27 -0
- package/types/orm/src/database.d.ts +46 -7
- package/types/orm/src/graph/graphUrl.d.ts +0 -8
- package/types/orm/src/index.d.ts +1 -1
- package/types/orm/src/query.d.ts +48 -2
- package/types/orm/src/realtime/storage.d.ts +2 -1
- package/types/orm/src/sqlTranslator.d.ts +70 -18
- package/types/orm/src/types.d.ts +6 -0
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
|
|
1
9
|
/**
|
|
2
10
|
* Tina4 WSDL/SOAP — SOAP 1.1 / WSDL 1.1 service base class.
|
|
3
11
|
*
|
|
@@ -71,122 +79,193 @@ function escapeXml(value: string): string {
|
|
|
71
79
|
.replace(/'/g, "'");
|
|
72
80
|
}
|
|
73
81
|
|
|
74
|
-
// ── Minimal zero-dep XML
|
|
75
|
-
//
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
// ── Minimal zero-dep XML parser ──────────────────────────────
|
|
83
|
+
// A small well-formedness-checking parser, enough for a SOAP envelope: it
|
|
84
|
+
// matches start and end tags, decodes the five predefined entities, decimal
|
|
85
|
+
// and hexadecimal character references and CDATA, skips comments and
|
|
86
|
+
// processing instructions, and refuses anything else (an undefined entity, a
|
|
87
|
+
// bare "&", a mismatched tag, a second root) the way Python's ElementTree
|
|
88
|
+
// does, so the handler can answer "Malformed XML" exactly where the reference
|
|
89
|
+
// does. Namespace prefixes are stripped to the local name. DOCTYPE is refused
|
|
90
|
+
// before this parser ever runs (see handle()).
|
|
91
|
+
|
|
92
|
+
interface XmlElement {
|
|
93
|
+
/** Local name, prefix stripped ("soap:Body" -> "Body"). */
|
|
94
|
+
name: string;
|
|
95
|
+
/** Qualified name as written, used to match the end tag. */
|
|
96
|
+
qualifiedName: string;
|
|
97
|
+
children: XmlElement[];
|
|
98
|
+
/** Text before the first child element - ElementTree's `.text`. */
|
|
81
99
|
text: string;
|
|
82
100
|
}
|
|
83
101
|
|
|
84
|
-
|
|
85
|
-
const root: XmlNode = { tag: "", children: [], text: "" };
|
|
86
|
-
const stack: XmlNode[] = [root];
|
|
87
|
-
let i = 0;
|
|
88
|
-
|
|
89
|
-
while (i < xml.length) {
|
|
90
|
-
if (xml[i] === "<") {
|
|
91
|
-
const closeIdx = xml.indexOf(">", i);
|
|
92
|
-
if (closeIdx === -1) break;
|
|
93
|
-
const tagContent = xml.substring(i + 1, closeIdx).trim();
|
|
94
|
-
|
|
95
|
-
if (tagContent.startsWith("/")) {
|
|
96
|
-
stack.pop();
|
|
97
|
-
} else if (tagContent.startsWith("?") || tagContent.startsWith("!")) {
|
|
98
|
-
// PI or comment — skip
|
|
99
|
-
} else {
|
|
100
|
-
const selfClosing = tagContent.endsWith("/");
|
|
101
|
-
const raw = selfClosing ? tagContent.slice(0, -1).trim() : tagContent;
|
|
102
|
-
const spaceIdx = raw.indexOf(" ");
|
|
103
|
-
const fullTag = spaceIdx === -1 ? raw : raw.substring(0, spaceIdx);
|
|
104
|
-
const colonIdx = fullTag.indexOf(":");
|
|
105
|
-
const localTag = colonIdx === -1 ? fullTag : fullTag.substring(colonIdx + 1);
|
|
106
|
-
|
|
107
|
-
const node: XmlNode = { tag: localTag, children: [], text: "" };
|
|
108
|
-
stack[stack.length - 1].children.push(node);
|
|
109
|
-
if (!selfClosing) stack.push(node);
|
|
110
|
-
}
|
|
111
|
-
i = closeIdx + 1;
|
|
112
|
-
} else {
|
|
113
|
-
const nextTag = xml.indexOf("<", i);
|
|
114
|
-
const text = (nextTag === -1 ? xml.substring(i) : xml.substring(i, nextTag)).trim();
|
|
115
|
-
if (text && stack.length > 1) {
|
|
116
|
-
stack[stack.length - 1].text += text;
|
|
117
|
-
}
|
|
118
|
-
i = nextTag === -1 ? xml.length : nextTag;
|
|
119
|
-
}
|
|
120
|
-
}
|
|
102
|
+
class MalformedXmlError extends Error {}
|
|
121
103
|
|
|
122
|
-
|
|
104
|
+
function malformed(): never {
|
|
105
|
+
throw new MalformedXmlError("Malformed XML");
|
|
123
106
|
}
|
|
124
107
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const found = findNode(child, tagName);
|
|
129
|
-
if (found) return found;
|
|
130
|
-
}
|
|
131
|
-
return null;
|
|
132
|
-
}
|
|
108
|
+
const PREDEFINED_ENTITIES: Record<string, string> = {
|
|
109
|
+
amp: "&", lt: "<", gt: ">", quot: '"', apos: "'",
|
|
110
|
+
};
|
|
133
111
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
// For complex content, fall back to regex on the original XML
|
|
141
|
-
const pattern = new RegExp(`<(?:[a-zA-Z0-9]+:)?${tagName}[^>]*>([\\s\\S]*?)</(?:[a-zA-Z0-9]+:)?${tagName}>`, "i");
|
|
142
|
-
const match = xml.match(pattern);
|
|
143
|
-
return match ? match[1] : node.text || null;
|
|
112
|
+
/** A code point the XML 1.0 Char production allows. */
|
|
113
|
+
function isXmlCharacter(codePoint: number): boolean {
|
|
114
|
+
return codePoint === 0x9 || codePoint === 0xa || codePoint === 0xd
|
|
115
|
+
|| (codePoint >= 0x20 && codePoint <= 0xd7ff)
|
|
116
|
+
|| (codePoint >= 0xe000 && codePoint <= 0xfffd)
|
|
117
|
+
|| (codePoint >= 0x10000 && codePoint <= 0x10ffff);
|
|
144
118
|
}
|
|
145
119
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
120
|
+
/** Any character XML 1.0 forbids (NUL, most C0 controls, U+FFFE/U+FFFF, a lone surrogate). */
|
|
121
|
+
const ILLEGAL_XML_CHARACTER = /[^\t\n\r\u0020-\uD7FF\uE000-\uFFFD\u{10000}-\u{10FFFF}]/u;
|
|
122
|
+
|
|
123
|
+
function decodeEntities(raw: string): string {
|
|
124
|
+
if (!raw.includes("&")) return raw;
|
|
125
|
+
return raw.replace(/&(#x[0-9a-fA-F]+|#[0-9]+|[A-Za-z_][\w.-]*)?(;)?/g, (_whole, reference?: string, semicolon?: string) => {
|
|
126
|
+
if (!reference || !semicolon) malformed();
|
|
127
|
+
if (reference.startsWith("#")) {
|
|
128
|
+
const codePoint = reference[1] === "x" ? parseInt(reference.slice(2), 16) : parseInt(reference.slice(1), 10);
|
|
129
|
+
if (!isXmlCharacter(codePoint)) malformed();
|
|
130
|
+
return String.fromCodePoint(codePoint);
|
|
131
|
+
}
|
|
132
|
+
const value = PREDEFINED_ENTITIES[reference];
|
|
133
|
+
if (value === undefined) malformed();
|
|
134
|
+
return value;
|
|
135
|
+
});
|
|
150
136
|
}
|
|
151
137
|
|
|
152
|
-
|
|
153
|
-
|
|
138
|
+
const XML_NAME = `[^\\s/<>="'&!?]+`;
|
|
139
|
+
const START_TAG = new RegExp(`<(${XML_NAME})((?:\\s+${XML_NAME}\\s*=\\s*(?:"[^"<]*"|'[^'<]*'))*)\\s*(/?)>`, "y");
|
|
140
|
+
const END_TAG = new RegExp(`</(${XML_NAME})\\s*>`, "y");
|
|
141
|
+
const ATTRIBUTE_VALUE = /=\s*(?:"([^"<]*)"|'([^'<]*)')/g;
|
|
142
|
+
|
|
143
|
+
function parseXmlDocument(xml: string): XmlElement {
|
|
144
|
+
const stack: XmlElement[] = [];
|
|
145
|
+
let root: XmlElement | null = null;
|
|
146
|
+
let position = 0;
|
|
147
|
+
|
|
148
|
+
const addText = (text: string): void => {
|
|
149
|
+
const current = stack[stack.length - 1];
|
|
150
|
+
if (current.children.length === 0) current.text += text;
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
while (position < xml.length) {
|
|
154
|
+
const nextTag = xml.indexOf("<", position);
|
|
155
|
+
const textEnd = nextTag === -1 ? xml.length : nextTag;
|
|
156
|
+
if (textEnd > position) {
|
|
157
|
+
const raw = xml.slice(position, textEnd);
|
|
158
|
+
if (stack.length > 0) addText(decodeEntities(raw));
|
|
159
|
+
else if (raw.trim() !== "") malformed(); // text outside the root element
|
|
160
|
+
position = textEnd;
|
|
161
|
+
if (nextTag === -1) break;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (xml.startsWith("<!--", position)) {
|
|
165
|
+
const close = xml.indexOf("-->", position + 4);
|
|
166
|
+
if (close === -1) malformed();
|
|
167
|
+
position = close + 3;
|
|
168
|
+
} else if (xml.startsWith("<![CDATA[", position)) {
|
|
169
|
+
const close = xml.indexOf("]]>", position + 9);
|
|
170
|
+
if (close === -1 || stack.length === 0) malformed();
|
|
171
|
+
addText(xml.slice(position + 9, close));
|
|
172
|
+
position = close + 3;
|
|
173
|
+
} else if (xml.startsWith("<?", position)) {
|
|
174
|
+
const close = xml.indexOf("?>", position + 2);
|
|
175
|
+
if (close === -1) malformed();
|
|
176
|
+
const target = xml.slice(position + 2, close).split(/\s/, 1)[0];
|
|
177
|
+
// The XML declaration is only legal as the very first thing in the document.
|
|
178
|
+
if (target.toLowerCase() === "xml" && position !== 0) malformed();
|
|
179
|
+
position = close + 2;
|
|
180
|
+
} else if (xml.startsWith("<!", position)) {
|
|
181
|
+
malformed(); // any other declaration; DOCTYPE was refused before parsing
|
|
182
|
+
} else if (xml.startsWith("</", position)) {
|
|
183
|
+
END_TAG.lastIndex = position;
|
|
184
|
+
const match = END_TAG.exec(xml);
|
|
185
|
+
const open = stack.pop();
|
|
186
|
+
if (!match || !open || open.qualifiedName !== match[1]) malformed();
|
|
187
|
+
position = END_TAG.lastIndex;
|
|
188
|
+
} else {
|
|
189
|
+
START_TAG.lastIndex = position;
|
|
190
|
+
const match = START_TAG.exec(xml);
|
|
191
|
+
if (!match || (stack.length === 0 && root !== null)) malformed(); // bad tag, or a second root
|
|
192
|
+
for (const attribute of match[2].matchAll(ATTRIBUTE_VALUE)) decodeEntities(attribute[1] ?? attribute[2]);
|
|
193
|
+
const qualifiedName = match[1];
|
|
194
|
+
const element: XmlElement = {
|
|
195
|
+
name: qualifiedName.slice(qualifiedName.indexOf(":") + 1),
|
|
196
|
+
qualifiedName,
|
|
197
|
+
children: [],
|
|
198
|
+
text: "",
|
|
199
|
+
};
|
|
200
|
+
if (stack.length > 0) stack[stack.length - 1].children.push(element);
|
|
201
|
+
else root = element;
|
|
202
|
+
if (match[3] !== "/") stack.push(element);
|
|
203
|
+
position = START_TAG.lastIndex;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
if (root === null || stack.length > 0) malformed();
|
|
208
|
+
return root;
|
|
154
209
|
}
|
|
155
210
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
211
|
+
/** The encoding named in the XML declaration, when the document opens with one. */
|
|
212
|
+
const XML_DECLARED_ENCODING = /^<\?xml\s[^>]*?\bencoding\s*=\s*(["'])(.*?)\1/;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Turn the request body into text, refusing anything that is not UTF-8 XML
|
|
216
|
+
* BEFORE any parse: bytes that are not valid UTF-8, a byte-order mark (a
|
|
217
|
+
* UTF-16 body starts with one), a character XML forbids (the NULs a UTF-16
|
|
218
|
+
* body leaves behind once decoded as UTF-8), or an XML declaration naming an
|
|
219
|
+
* encoding other than UTF-8. A DOCTYPE hidden in UTF-16 or UTF-7 is therefore
|
|
220
|
+
* refused here, never parsed.
|
|
221
|
+
*/
|
|
222
|
+
function decodeSoapBody(body: string | Uint8Array): string {
|
|
223
|
+
let text: string;
|
|
224
|
+
if (typeof body === "string") {
|
|
225
|
+
text = body;
|
|
226
|
+
} else {
|
|
227
|
+
try {
|
|
228
|
+
text = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(body);
|
|
229
|
+
} catch {
|
|
230
|
+
malformed();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (text.startsWith("\uFEFF") || ILLEGAL_XML_CHARACTER.test(text)) malformed();
|
|
234
|
+
// The body IS UTF-8, so a declaration naming any other encoding (UTF-7,
|
|
235
|
+
// UTF-16, ISO-8859-1, even "UTF8") is refused rather than trusted: a parser
|
|
236
|
+
// that honours it would read a different document from the one checked here.
|
|
237
|
+
const declaredEncoding = XML_DECLARED_ENCODING.exec(text);
|
|
238
|
+
if (declaredEncoding && declaredEncoding[2].toUpperCase() !== "UTF-8") malformed();
|
|
239
|
+
return text;
|
|
163
240
|
}
|
|
164
241
|
|
|
165
242
|
// ── Metadata storage ─────────────────────────────────────────
|
|
166
243
|
|
|
167
|
-
/** Symbol key for storing operation metadata on class prototypes. */
|
|
168
|
-
const WSDL_OPS_KEY = Symbol("wsdl_operations");
|
|
169
|
-
|
|
170
244
|
/**
|
|
171
245
|
* Decorator function for marking methods as WSDL operations.
|
|
172
246
|
*
|
|
173
247
|
* @WSDLOperation({ description: "Add two numbers", input: { a: "int", b: "int" }, output: { Result: "int" } })
|
|
174
248
|
* async Add(a: number, b: number): Promise<Record<string, unknown>> { ... }
|
|
249
|
+
*
|
|
250
|
+
* Works under both decorator flavours: TC39 standard decorators (what tsx and
|
|
251
|
+
* esbuild compile by default: `(method, context)`) and TypeScript's
|
|
252
|
+
* experimentalDecorators (`(prototype, name, descriptor)`).
|
|
175
253
|
*/
|
|
176
254
|
export function WSDLOperation(config?: WSDLOperationConfig) {
|
|
177
|
-
return function (
|
|
255
|
+
return function (
|
|
256
|
+
target: unknown,
|
|
257
|
+
context: string | symbol | { name: string | symbol },
|
|
258
|
+
descriptor?: PropertyDescriptor,
|
|
259
|
+
): PropertyDescriptor | undefined {
|
|
260
|
+
const method = (descriptor ? descriptor.value : target) as { _wsdlOp?: WSDLOperationMeta };
|
|
261
|
+
const name = typeof context === "object" ? context.name : context;
|
|
178
262
|
// Store metadata on the method itself
|
|
179
|
-
|
|
180
|
-
name:
|
|
263
|
+
method._wsdlOp ??= {
|
|
264
|
+
name: String(name),
|
|
181
265
|
description: config?.description,
|
|
182
266
|
input: config?.input,
|
|
183
267
|
output: config?.output,
|
|
184
268
|
};
|
|
185
|
-
|
|
186
|
-
if (!descriptor.value._wsdlOp) {
|
|
187
|
-
descriptor.value._wsdlOp = op;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
269
|
return descriptor;
|
|
191
270
|
};
|
|
192
271
|
}
|
|
@@ -403,27 +482,41 @@ export abstract class WSDLService {
|
|
|
403
482
|
/**
|
|
404
483
|
* Handle incoming SOAP request (parse XML, dispatch to method, return SOAP response).
|
|
405
484
|
*/
|
|
406
|
-
async handle(soapXml: string = ""): Promise<string> {
|
|
485
|
+
async handle(soapXml: string | Uint8Array = ""): Promise<string> {
|
|
407
486
|
const ops = this.discoverOperations();
|
|
408
487
|
|
|
488
|
+
// Bytes that are not UTF-8 XML (a UTF-16 body, a byte-order mark) are
|
|
489
|
+
// refused before anything looks inside them.
|
|
490
|
+
let xml: string;
|
|
491
|
+
try {
|
|
492
|
+
xml = decodeSoapBody(soapXml);
|
|
493
|
+
} catch {
|
|
494
|
+
return this.soapFault("Client", "Malformed XML");
|
|
495
|
+
}
|
|
496
|
+
|
|
409
497
|
// SOAP 1.1 (§3) forbids a Document Type Declaration in a SOAP message.
|
|
410
498
|
// Rejecting any DOCTYPE/DTD up front — BEFORE the body is parsed — also
|
|
411
499
|
// closes the XML entity-expansion (billion-laughs) and external-entity
|
|
412
500
|
// (XXE) attack surface regardless of parser internals. The operation
|
|
413
|
-
// never runs.
|
|
414
|
-
|
|
415
|
-
if (/<!DOCTYPE/i.test(soapXml)) {
|
|
501
|
+
// never runs.
|
|
502
|
+
if (/<!DOCTYPE/i.test(xml)) {
|
|
416
503
|
return this.soapFault("Client", "DOCTYPE declarations are not allowed in SOAP messages");
|
|
417
504
|
}
|
|
418
505
|
|
|
419
|
-
|
|
420
|
-
|
|
506
|
+
let envelope: XmlElement;
|
|
507
|
+
try {
|
|
508
|
+
envelope = parseXmlDocument(xml);
|
|
509
|
+
} catch {
|
|
510
|
+
return this.soapFault("Client", "Malformed XML");
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// The Body is a direct child of the envelope; its first child is the operation.
|
|
514
|
+
const body = envelope.children.find((child) => child.name === "Body");
|
|
421
515
|
if (!body) {
|
|
422
516
|
return this.soapFault("Client", "Missing SOAP Body");
|
|
423
517
|
}
|
|
424
518
|
|
|
425
|
-
|
|
426
|
-
const operation = extractOperation(body);
|
|
519
|
+
const operation = body.children[0];
|
|
427
520
|
if (!operation) {
|
|
428
521
|
return this.soapFault("Client", "Empty SOAP Body");
|
|
429
522
|
}
|
|
@@ -447,15 +540,14 @@ export abstract class WSDLService {
|
|
|
447
540
|
// non-numeric value for an int/float param (convertValue throws, matching
|
|
448
541
|
// Python's int()/float() raise) becomes a Server fault — not a silent NaN.
|
|
449
542
|
try {
|
|
450
|
-
// Extract parameters from the operation element
|
|
451
|
-
const children = extractChildren(operation.content);
|
|
543
|
+
// Extract parameters from the operation element's children
|
|
452
544
|
const params: unknown[] = [];
|
|
453
545
|
|
|
454
546
|
if (opMeta.input) {
|
|
455
547
|
for (const [paramName, paramType] of Object.entries(opMeta.input)) {
|
|
456
|
-
const child = children.find((c) => c.name === paramName);
|
|
548
|
+
const child = operation.children.find((c) => c.name === paramName);
|
|
457
549
|
if (child) {
|
|
458
|
-
params.push(this.convertValue(child.
|
|
550
|
+
params.push(this.convertValue(child.text, paramType));
|
|
459
551
|
} else {
|
|
460
552
|
params.push(null);
|
|
461
553
|
}
|
|
@@ -549,29 +641,19 @@ export abstract class WSDLService {
|
|
|
549
641
|
* Handle POST request — process SOAP XML.
|
|
550
642
|
*/
|
|
551
643
|
private async handlePostRequest(req: Record<string, unknown>, res: Record<string, unknown>): Promise<void> {
|
|
552
|
-
let xmlBody = "";
|
|
644
|
+
let xmlBody: string | Uint8Array = "";
|
|
553
645
|
|
|
554
|
-
// Try to get body from request object
|
|
555
|
-
if (typeof req.rawBody === "string") {
|
|
646
|
+
// Try to get body from request object (raw bytes are checked for UTF-8 in handle())
|
|
647
|
+
if (typeof req.rawBody === "string" || req.rawBody instanceof Uint8Array) {
|
|
556
648
|
xmlBody = req.rawBody;
|
|
557
|
-
} else if (typeof req.body === "string") {
|
|
649
|
+
} else if (typeof req.body === "string" || req.body instanceof Uint8Array) {
|
|
558
650
|
xmlBody = req.body;
|
|
559
651
|
} else if (typeof req.body === "object" && req.body !== null) {
|
|
560
652
|
xmlBody = JSON.stringify(req.body);
|
|
561
653
|
}
|
|
562
654
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
if (typeof res.send === "function") {
|
|
566
|
-
if (typeof res.status === "function") (res.status as Function)(400);
|
|
567
|
-
if (typeof res.setHeader === "function") {
|
|
568
|
-
(res.setHeader as Function)("Content-Type", "text/xml; charset=UTF-8");
|
|
569
|
-
}
|
|
570
|
-
(res.send as Function)(fault);
|
|
571
|
-
}
|
|
572
|
-
return;
|
|
573
|
-
}
|
|
574
|
-
|
|
655
|
+
// An empty body is not XML: handle() answers it with the same Client
|
|
656
|
+
// "Malformed XML" fault as any other unparsable body (Python parity).
|
|
575
657
|
const soapResponse = await this.handle(xmlBody);
|
|
576
658
|
|
|
577
659
|
if (typeof res.send === "function") {
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
#}
|
|
8
|
+
|
|
1
9
|
{% extends "errors/base.twig" %}
|
|
2
10
|
{% block title %}Redirecting…{% endblock %}
|
|
3
11
|
{% block meta %}<meta http-equiv="refresh" content="0;url={{ redirect_url }}">{% endblock %}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
#}
|
|
8
|
+
|
|
1
9
|
{% extends "errors/base.twig" %}
|
|
2
10
|
{% block title %}401 — Unauthorized{% endblock %}
|
|
3
11
|
{% block content %}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
+
{#
|
|
3
|
+
Copyright (c) 2026 Code Infinity
|
|
4
|
+
SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
6
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
7
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
8
|
+
#}
|
|
9
|
+
|
|
2
10
|
<html lang="en">
|
|
3
11
|
<head>
|
|
4
12
|
<meta charset="utf-8">
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
+
{#
|
|
3
|
+
Copyright (c) 2026 Code Infinity
|
|
4
|
+
SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
6
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
7
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
8
|
+
#}
|
|
9
|
+
|
|
2
10
|
<html lang="en">
|
|
3
11
|
<head>
|
|
4
12
|
<meta charset="utf-8">
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
+
{#
|
|
3
|
+
Copyright (c) 2026 Code Infinity
|
|
4
|
+
SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
6
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
7
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
8
|
+
#}
|
|
9
|
+
|
|
2
10
|
<html lang="en">
|
|
3
11
|
<head>
|
|
4
12
|
<meta charset="utf-8">
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
#}
|
|
8
|
+
|
|
1
9
|
{% extends "errors/base.twig" %}
|
|
2
10
|
{% block title %}502 — Bad Gateway{% endblock %}
|
|
3
11
|
{% block content %}
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
{#
|
|
2
|
+
Copyright (c) 2026 Code Infinity
|
|
3
|
+
SPDX-License-Identifier: MPL-2.0
|
|
4
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
#}
|
|
8
|
+
|
|
1
9
|
{% extends "errors/base.twig" %}
|
|
2
10
|
{% block title %}503 — Service Unavailable{% endblock %}
|
|
3
11
|
{% block extra_styles %}
|
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
|
+
{#
|
|
3
|
+
Copyright (c) 2026 Code Infinity
|
|
4
|
+
SPDX-License-Identifier: MPL-2.0
|
|
5
|
+
This Source Code Form is subject to the terms of the Mozilla Public
|
|
6
|
+
License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
7
|
+
file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
8
|
+
#}
|
|
9
|
+
|
|
2
10
|
<html lang="en">
|
|
3
11
|
<head>
|
|
4
12
|
<meta charset="UTF-8">
|