botversion-sdk 2.2.2 → 2.2.3
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/interceptor.js +50 -9
- package/package.json +1 -1
- package/scanner.js +302 -13
package/interceptor.js
CHANGED
|
@@ -215,6 +215,20 @@ function parseCapturedBody(buffer, res) {
|
|
|
215
215
|
return undefined;
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// True when the request carries an upload (multipart) body that cannot be read safely
|
|
219
|
+
function isMultipartContentType(value) {
|
|
220
|
+
return String(value || "")
|
|
221
|
+
.toLowerCase()
|
|
222
|
+
.includes("multipart/form-data");
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Adds the multipart marker to a request schema (creates an empty one if there are no fields)
|
|
226
|
+
function withMultipartMarker(schema) {
|
|
227
|
+
const out = schema || { type: "object", properties: {} };
|
|
228
|
+
out["x-content-type"] = "multipart/form-data";
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
|
|
218
232
|
// Our own scan-trigger route must never be reported as one of the host's endpoints
|
|
219
233
|
function isBotVersionInternalPath(path) {
|
|
220
234
|
return String(path || "").indexOf("/__botversion/") === 0;
|
|
@@ -522,7 +536,16 @@ function attachInterceptor(app, client, options) {
|
|
|
522
536
|
if (!reportedEndpoints.has(bodyKey)) {
|
|
523
537
|
reportedEndpoints.add(bodyKey);
|
|
524
538
|
|
|
525
|
-
|
|
539
|
+
let jsonSchema = structureToJsonSchema(bodyStructure);
|
|
540
|
+
// Upload bodies can't be read here, so flag them as multipart instead of "no input"
|
|
541
|
+
if (
|
|
542
|
+
String((req.headers && req.headers["content-type"]) || "")
|
|
543
|
+
.toLowerCase()
|
|
544
|
+
.includes("multipart/form-data")
|
|
545
|
+
) {
|
|
546
|
+
jsonSchema = jsonSchema || { type: "object", properties: {} };
|
|
547
|
+
jsonSchema["x-content-type"] = "multipart/form-data";
|
|
548
|
+
}
|
|
526
549
|
|
|
527
550
|
setImmediate(function () {
|
|
528
551
|
client
|
|
@@ -852,14 +875,18 @@ function attachNextJsInterceptor(client, options) {
|
|
|
852
875
|
if (!reportedEndpoints.has(endpointKey)) {
|
|
853
876
|
reportedEndpoints.add(endpointKey);
|
|
854
877
|
|
|
855
|
-
// Skip file uploads — too large and not JSON
|
|
856
878
|
const contentType = req.headers["content-type"] || "";
|
|
857
879
|
if (contentType.includes("multipart/form-data")) {
|
|
858
880
|
client
|
|
859
881
|
.updateEndpoint({
|
|
860
882
|
method: method,
|
|
861
883
|
path: normalizedPath,
|
|
862
|
-
|
|
884
|
+
// Field names can't be read without consuming the upload stream, so flag it as multipart instead of "no input"
|
|
885
|
+
requestBody: {
|
|
886
|
+
type: "object",
|
|
887
|
+
properties: {},
|
|
888
|
+
"x-content-type": "multipart/form-data",
|
|
889
|
+
},
|
|
863
890
|
detectedBy: "runtime",
|
|
864
891
|
})
|
|
865
892
|
.catch(function () {});
|
|
@@ -1078,9 +1105,13 @@ function attachFastifyInterceptor(fastify, client, options) {
|
|
|
1078
1105
|
.updateEndpoint({
|
|
1079
1106
|
method,
|
|
1080
1107
|
path: normalizedPath,
|
|
1081
|
-
requestBody:
|
|
1082
|
-
?
|
|
1083
|
-
|
|
1108
|
+
requestBody: isMultipartContentType(request.headers["content-type"])
|
|
1109
|
+
? withMultipartMarker(
|
|
1110
|
+
queryStructure ? structureToJsonSchema(queryStructure) : null,
|
|
1111
|
+
)
|
|
1112
|
+
: queryStructure
|
|
1113
|
+
? structureToJsonSchema(queryStructure)
|
|
1114
|
+
: null,
|
|
1084
1115
|
detectedBy: "runtime-fastify",
|
|
1085
1116
|
})
|
|
1086
1117
|
.catch(function () {});
|
|
@@ -1100,6 +1131,8 @@ function attachFastifyInterceptor(fastify, client, options) {
|
|
|
1100
1131
|
|
|
1101
1132
|
const body = request.body;
|
|
1102
1133
|
if (!body) return;
|
|
1134
|
+
// Upload bodies are reported as multipart by the first hook; their parts are not plain fields
|
|
1135
|
+
if (isMultipartContentType(request.headers["content-type"])) return;
|
|
1103
1136
|
|
|
1104
1137
|
const bodyStructure = buildBodyStructure(body);
|
|
1105
1138
|
if (!bodyStructure) return;
|
|
@@ -1247,7 +1280,9 @@ function attachKoaInterceptor(app, client, options) {
|
|
|
1247
1280
|
.updateEndpoint({
|
|
1248
1281
|
method,
|
|
1249
1282
|
path: normalizedPath,
|
|
1250
|
-
requestBody:
|
|
1283
|
+
requestBody: isMultipartContentType(ctx.headers["content-type"])
|
|
1284
|
+
? withMultipartMarker(jsonSchema)
|
|
1285
|
+
: jsonSchema,
|
|
1251
1286
|
detectedBy: "runtime-koa",
|
|
1252
1287
|
})
|
|
1253
1288
|
.catch(function () {});
|
|
@@ -1324,7 +1359,11 @@ function attachHapiInterceptor(server, client, options) {
|
|
|
1324
1359
|
const normalizedPath = normalizePath(path);
|
|
1325
1360
|
const endpointKey = method + ":" + normalizedPath;
|
|
1326
1361
|
|
|
1327
|
-
|
|
1362
|
+
// Upload payloads hold file streams, not plain fields, so they are never read as a body
|
|
1363
|
+
const isUpload = isMultipartContentType(
|
|
1364
|
+
request.headers && request.headers["content-type"],
|
|
1365
|
+
);
|
|
1366
|
+
const body = isUpload ? null : request.payload;
|
|
1328
1367
|
// GET/DELETE inputs live in the query string, so their names are added too
|
|
1329
1368
|
const bodyStructure = mergeQueryIntoStructure(
|
|
1330
1369
|
buildBodyStructure(body),
|
|
@@ -1352,7 +1391,9 @@ function attachHapiInterceptor(server, client, options) {
|
|
|
1352
1391
|
.updateEndpoint({
|
|
1353
1392
|
method,
|
|
1354
1393
|
path: normalizedPath,
|
|
1355
|
-
requestBody:
|
|
1394
|
+
requestBody: isUpload
|
|
1395
|
+
? withMultipartMarker(jsonSchema)
|
|
1396
|
+
: jsonSchema,
|
|
1356
1397
|
detectedBy: "runtime-hapi",
|
|
1357
1398
|
})
|
|
1358
1399
|
.catch(function () {});
|
package/package.json
CHANGED
package/scanner.js
CHANGED
|
@@ -80,7 +80,19 @@ function scanExpressFileStatically(filePath, seen) {
|
|
|
80
80
|
method: effectiveMethod,
|
|
81
81
|
path: routePath,
|
|
82
82
|
description: "",
|
|
83
|
-
requestBody:
|
|
83
|
+
requestBody:
|
|
84
|
+
mergeBodyWithMultipart(
|
|
85
|
+
bodyFields,
|
|
86
|
+
["POST", "PUT", "PATCH"].includes(effectiveMethod)
|
|
87
|
+
? extractMultipartFieldsFromFile(
|
|
88
|
+
sliceRouteSegment(content, match.index, routePattern.source),
|
|
89
|
+
) ||
|
|
90
|
+
((content.match(new RegExp(routePattern.source, "gi")) || [])
|
|
91
|
+
.length === 1
|
|
92
|
+
? extractMultipartFieldsFromFile(content)
|
|
93
|
+
: null)
|
|
94
|
+
: null,
|
|
95
|
+
) || queryFields,
|
|
84
96
|
routeParamMap: routeParamMap,
|
|
85
97
|
responseBody: extractResponseFieldsForRoute(
|
|
86
98
|
content,
|
|
@@ -239,6 +251,13 @@ function scanNextJsRoutes(pagesDir) {
|
|
|
239
251
|
: null);
|
|
240
252
|
const queryFields = extractQueryFieldsFromFile(methodCode);
|
|
241
253
|
|
|
254
|
+
// Upload handlers read multipart fields, not req.body
|
|
255
|
+
const multipartFields =
|
|
256
|
+
method !== "GET" && method !== "DELETE"
|
|
257
|
+
? extractMultipartFieldsFromFile(methodCode) ||
|
|
258
|
+
extractMultipartFieldsFromFile(fileContent)
|
|
259
|
+
: null;
|
|
260
|
+
|
|
242
261
|
// GET inputs come from the query string; only this method's own code is read
|
|
243
262
|
const getQueryFields =
|
|
244
263
|
method === "GET"
|
|
@@ -252,7 +271,7 @@ function scanNextJsRoutes(pagesDir) {
|
|
|
252
271
|
|
|
253
272
|
// For DELETE with no body fields, query params are the input
|
|
254
273
|
const effectiveRequestBody =
|
|
255
|
-
bodyFields ||
|
|
274
|
+
mergeBodyWithMultipart(bodyFields, multipartFields) ||
|
|
256
275
|
(method === "DELETE" && queryFields ? queryFields : null) ||
|
|
257
276
|
getQueryFields;
|
|
258
277
|
|
|
@@ -462,6 +481,197 @@ function extractBodyFieldsFromFile(content) {
|
|
|
462
481
|
return { type: "object", properties };
|
|
463
482
|
}
|
|
464
483
|
|
|
484
|
+
// Reads field names from multipart/file-upload handlers (formData, multer, formidable).
|
|
485
|
+
// Returns null when the file is not an upload handler, so other routes are unaffected.
|
|
486
|
+
function extractMultipartFieldsFromFile(content) {
|
|
487
|
+
const text = String(content || "");
|
|
488
|
+
const usesFormData =
|
|
489
|
+
/formData\s*\(|readFormData\s*\(|parseMultipartFormData\s*\(/.test(text);
|
|
490
|
+
const usesMulter = /multer|upload\.(single|array|fields|any)\s*\(/.test(text);
|
|
491
|
+
const usesFormidable = /formidable|koa-?body/i.test(text);
|
|
492
|
+
// NestJS interceptors, AdonisJS request.file("x"), Nuxt readMultipartFormData, Hapi multipart payloads
|
|
493
|
+
const usesNest =
|
|
494
|
+
/\b(?:FileInterceptor|FilesInterceptor|FileFieldsInterceptor|AnyFilesInterceptor)\s*\(/.test(
|
|
495
|
+
text,
|
|
496
|
+
);
|
|
497
|
+
const usesRequestFile = /\brequest\.(?:file|files)\s*\(\s*['"`]/.test(text);
|
|
498
|
+
const usesNuxtParts = /readMultipartFormData\s*\(/.test(text);
|
|
499
|
+
const usesHapiPayload = /\bmultipart\s*:\s*(?:true|\{)/.test(text);
|
|
500
|
+
const usesOtherUpload =
|
|
501
|
+
usesNest || usesRequestFile || usesNuxtParts || usesHapiPayload;
|
|
502
|
+
if (!usesFormData && !usesMulter && !usesFormidable && !usesOtherUpload)
|
|
503
|
+
return null;
|
|
504
|
+
|
|
505
|
+
const fields = {}; // name -> "file" | "string"
|
|
506
|
+
|
|
507
|
+
if (usesFormData) {
|
|
508
|
+
// Only read .get() calls on the variable that holds the parsed form,
|
|
509
|
+
// so headers.get(...) / searchParams.get(...) are never mistaken for form fields
|
|
510
|
+
const formVars = [];
|
|
511
|
+
for (const fv of text.matchAll(
|
|
512
|
+
/(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:await\s+)?(?:[\w$.]+\.formData|readFormData|(?:unstable_)?parseMultipartFormData)\s*\(/g,
|
|
513
|
+
)) {
|
|
514
|
+
formVars.push(fv[1]);
|
|
515
|
+
}
|
|
516
|
+
const readFormGet = function (m) {
|
|
517
|
+
const varName = m[1];
|
|
518
|
+
const fieldName = m[2];
|
|
519
|
+
let kind = "string";
|
|
520
|
+
if (varName) {
|
|
521
|
+
const fileUse = new RegExp(
|
|
522
|
+
"\\b" +
|
|
523
|
+
varName +
|
|
524
|
+
"\\b\\s*(?:as\\s+File|instanceof\\s+File|\\.(?:arrayBuffer|stream|size|type|name)\\b)|instanceof\\s+File",
|
|
525
|
+
);
|
|
526
|
+
if (fileUse.test(text)) kind = "file";
|
|
527
|
+
}
|
|
528
|
+
if (fields[fieldName] !== "file") fields[fieldName] = kind;
|
|
529
|
+
};
|
|
530
|
+
formVars.forEach(function (formVar) {
|
|
531
|
+
const safeVar = formVar.replace(/\$/g, "\\$");
|
|
532
|
+
const getRe = new RegExp(
|
|
533
|
+
"(?:(?:const|let|var)\\s+([A-Za-z_$][\\w$]*)\\s*=\\s*)?\\b" +
|
|
534
|
+
safeVar +
|
|
535
|
+
"\\s*\\.(?:get|getAll)\\s*\\(\\s*['\"`]([^'\"`]+)['\"`]\\s*\\)",
|
|
536
|
+
"g",
|
|
537
|
+
);
|
|
538
|
+
for (const m of text.matchAll(getRe)) readFormGet(m);
|
|
539
|
+
});
|
|
540
|
+
// Inline form: (await request.formData()).get("file")
|
|
541
|
+
const inlineRe =
|
|
542
|
+
/(?:(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*)?\(\s*await\s+[\w$.]+\.formData\s*\(\s*\)\s*\)\s*\.(?:get|getAll)\s*\(\s*['"`]([^'"`]+)['"`]\s*\)/g;
|
|
543
|
+
for (const m of text.matchAll(inlineRe)) readFormGet(m);
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (usesMulter) {
|
|
547
|
+
// upload.single("file") / upload.array("files")
|
|
548
|
+
for (const m of text.matchAll(
|
|
549
|
+
/\.(?:single|array)\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
550
|
+
)) {
|
|
551
|
+
fields[m[1]] = "file";
|
|
552
|
+
}
|
|
553
|
+
// upload.fields([{ name: "avatar" }, { name: "docs" }])
|
|
554
|
+
for (const block of text.matchAll(/\.fields\s*\(\s*\[([\s\S]*?)\]\s*\)/g)) {
|
|
555
|
+
for (const n of block[1].matchAll(/name\s*:\s*['"`]([^'"`]+)['"`]/g)) {
|
|
556
|
+
fields[n[1]] = "file";
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
if (usesFormidable) {
|
|
562
|
+
// files.file / files["file"] and fields.title / fields["title"]
|
|
563
|
+
for (const m of text.matchAll(
|
|
564
|
+
/\bfiles(?:\?\.|\.)([A-Za-z_$][\w$]*)|\bfiles\s*\[\s*['"`]([^'"`]+)['"`]\s*\]/g,
|
|
565
|
+
)) {
|
|
566
|
+
fields[m[1] || m[2]] = "file";
|
|
567
|
+
}
|
|
568
|
+
for (const m of text.matchAll(
|
|
569
|
+
/\bfields(?:\?\.|\.)([A-Za-z_$][\w$]*)|\bfields\s*\[\s*['"`]([^'"`]+)['"`]\s*\]/g,
|
|
570
|
+
)) {
|
|
571
|
+
const name = m[1] || m[2];
|
|
572
|
+
if (!fields[name]) fields[name] = "string";
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
if (usesNest) {
|
|
577
|
+
// @UseInterceptors(FileInterceptor("file")) / FilesInterceptor("files")
|
|
578
|
+
for (const m of text.matchAll(
|
|
579
|
+
/\b(?:FileInterceptor|FilesInterceptor)\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
580
|
+
)) {
|
|
581
|
+
fields[m[1]] = "file";
|
|
582
|
+
}
|
|
583
|
+
// FileFieldsInterceptor([{ name: "avatar" }, { name: "docs" }])
|
|
584
|
+
for (const block of text.matchAll(
|
|
585
|
+
/\bFileFieldsInterceptor\s*\(\s*\[([\s\S]*?)\]/g,
|
|
586
|
+
)) {
|
|
587
|
+
for (const n of block[1].matchAll(/name\s*:\s*['"`]([^'"`]+)['"`]/g)) {
|
|
588
|
+
fields[n[1]] = "file";
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
if (usesRequestFile) {
|
|
594
|
+
// request.file("avatar") / request.files("docs")
|
|
595
|
+
for (const m of text.matchAll(
|
|
596
|
+
/\brequest\.(?:file|files)\s*\(\s*['"`]([^'"`]+)['"`]/g,
|
|
597
|
+
)) {
|
|
598
|
+
fields[m[1]] = "file";
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
if (usesNuxtParts) {
|
|
603
|
+
// readMultipartFormData(event) -> parts.find((p) => p.name === "file")
|
|
604
|
+
for (const m of text.matchAll(
|
|
605
|
+
/(?:(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*)?[^\n;]*?\.name\s*===?\s*['"`]([^'"`]+)['"`]/g,
|
|
606
|
+
)) {
|
|
607
|
+
let kind = "string";
|
|
608
|
+
if (m[1]) {
|
|
609
|
+
const safeVar = m[1].replace(/\$/g, "\\$");
|
|
610
|
+
const fileUse = new RegExp(
|
|
611
|
+
"\\b" + safeVar + "\\b\\s*(?:\\?\\.|\\.)\\s*(?:filename|type)\\b",
|
|
612
|
+
);
|
|
613
|
+
if (fileUse.test(text)) kind = "file";
|
|
614
|
+
}
|
|
615
|
+
if (fields[m[2]] !== "file") fields[m[2]] = kind;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
if (usesHapiPayload) {
|
|
620
|
+
// request.payload.file / const { file, title } = request.payload
|
|
621
|
+
const addHapiField = function (name) {
|
|
622
|
+
if (!/^[A-Za-z_$][\w$]*$/.test(name) || fields[name]) return;
|
|
623
|
+
const safeName = name.replace(/\$/g, "\\$");
|
|
624
|
+
const fileLike = new RegExp(
|
|
625
|
+
"\\b" + safeName + "\\b\\s*(?:\\?\\.|\\.)\\s*(?:hapi|_data|pipe)\\b",
|
|
626
|
+
).test(text);
|
|
627
|
+
fields[name] = fileLike ? "file" : "string";
|
|
628
|
+
};
|
|
629
|
+
for (const m of text.matchAll(/\bpayload(?:\?\.|\.)([A-Za-z_$][\w$]*)/g)) {
|
|
630
|
+
addHapiField(m[1]);
|
|
631
|
+
}
|
|
632
|
+
for (const d of text.matchAll(/\{([^}]+)\}\s*=\s*[\w$.]*\bpayload\b/g)) {
|
|
633
|
+
d[1].split(",").forEach(function (f) {
|
|
634
|
+
addHapiField(f.trim().split(":")[0].split("=")[0].trim());
|
|
635
|
+
});
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
const names = Object.keys(fields);
|
|
640
|
+
if (names.length === 0) return null;
|
|
641
|
+
|
|
642
|
+
const properties = {};
|
|
643
|
+
names.forEach(function (n) {
|
|
644
|
+
properties[n] =
|
|
645
|
+
fields[n] === "file"
|
|
646
|
+
? { type: "string", format: "binary" }
|
|
647
|
+
: { type: "string" };
|
|
648
|
+
});
|
|
649
|
+
return {
|
|
650
|
+
type: "object",
|
|
651
|
+
properties: properties,
|
|
652
|
+
"x-content-type": "multipart/form-data",
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// Start of any Fastify route definition, used to cut a file into per-route pieces
|
|
657
|
+
const FASTIFY_ROUTE_START =
|
|
658
|
+
"(?:fastify|app|server|router)\\.(?:get|post|put|delete|patch|route)\\s*\\(";
|
|
659
|
+
|
|
660
|
+
// Combines text fields read from req.body with file fields from the upload parser
|
|
661
|
+
function mergeBodyWithMultipart(bodyFields, multipartFields) {
|
|
662
|
+
if (!multipartFields) return bodyFields;
|
|
663
|
+
if (!bodyFields) return multipartFields;
|
|
664
|
+
return {
|
|
665
|
+
type: "object",
|
|
666
|
+
properties: Object.assign(
|
|
667
|
+
{},
|
|
668
|
+
bodyFields.properties,
|
|
669
|
+
multipartFields.properties,
|
|
670
|
+
),
|
|
671
|
+
"x-content-type": "multipart/form-data",
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
|
|
465
675
|
function scanNextJsAppRoutes(appDir) {
|
|
466
676
|
const fs = require("fs");
|
|
467
677
|
const path = require("path");
|
|
@@ -507,6 +717,11 @@ function scanNextJsAppRoutes(appDir) {
|
|
|
507
717
|
? extractAppRouterBodyFields(content)
|
|
508
718
|
: null);
|
|
509
719
|
const queryFields = extractQueryFieldsFromFile(methodCode);
|
|
720
|
+
const multipartFields =
|
|
721
|
+
method !== "GET" && method !== "DELETE"
|
|
722
|
+
? extractMultipartFieldsFromFile(methodCode) ||
|
|
723
|
+
extractMultipartFieldsFromFile(content)
|
|
724
|
+
: null;
|
|
510
725
|
|
|
511
726
|
const routeParamMap = buildRouteParamMap(routePath, []);
|
|
512
727
|
|
|
@@ -515,7 +730,7 @@ function scanNextJsAppRoutes(appDir) {
|
|
|
515
730
|
path: routePath,
|
|
516
731
|
description: "",
|
|
517
732
|
requestBody:
|
|
518
|
-
bodyFields ||
|
|
733
|
+
mergeBodyWithMultipart(bodyFields, multipartFields) ||
|
|
519
734
|
(method === "DELETE" && queryFields ? queryFields : null) ||
|
|
520
735
|
(method === "GET"
|
|
521
736
|
? removePathParamFields(
|
|
@@ -1548,7 +1763,14 @@ function scanFastifyRoutes(cwd) {
|
|
|
1548
1763
|
method,
|
|
1549
1764
|
path: routePath,
|
|
1550
1765
|
description: "",
|
|
1551
|
-
requestBody:
|
|
1766
|
+
requestBody: mergeBodyWithMultipart(
|
|
1767
|
+
bodyFields,
|
|
1768
|
+
needsBody
|
|
1769
|
+
? extractMultipartFieldsFromFile(
|
|
1770
|
+
sliceRouteSegment(content, match.index, FASTIFY_ROUTE_START),
|
|
1771
|
+
)
|
|
1772
|
+
: null,
|
|
1773
|
+
),
|
|
1552
1774
|
routeParamMap,
|
|
1553
1775
|
detectedBy: "static-scan-fastify",
|
|
1554
1776
|
});
|
|
@@ -1577,7 +1799,14 @@ function scanFastifyRoutes(cwd) {
|
|
|
1577
1799
|
method,
|
|
1578
1800
|
path: routePath,
|
|
1579
1801
|
description: "",
|
|
1580
|
-
requestBody:
|
|
1802
|
+
requestBody: mergeBodyWithMultipart(
|
|
1803
|
+
bodyFields,
|
|
1804
|
+
needsBody
|
|
1805
|
+
? extractMultipartFieldsFromFile(
|
|
1806
|
+
sliceRouteSegment(content, match.index, FASTIFY_ROUTE_START),
|
|
1807
|
+
)
|
|
1808
|
+
: null,
|
|
1809
|
+
),
|
|
1581
1810
|
routeParamMap,
|
|
1582
1811
|
detectedBy: "static-scan-fastify",
|
|
1583
1812
|
});
|
|
@@ -1613,7 +1842,18 @@ function scanFastifyRoutes(cwd) {
|
|
|
1613
1842
|
method,
|
|
1614
1843
|
path: routePath,
|
|
1615
1844
|
description: "",
|
|
1616
|
-
requestBody:
|
|
1845
|
+
requestBody: mergeBodyWithMultipart(
|
|
1846
|
+
bodyFields,
|
|
1847
|
+
needsBody
|
|
1848
|
+
? extractMultipartFieldsFromFile(
|
|
1849
|
+
sliceRouteSegment(
|
|
1850
|
+
content,
|
|
1851
|
+
match.index,
|
|
1852
|
+
FASTIFY_ROUTE_START,
|
|
1853
|
+
),
|
|
1854
|
+
)
|
|
1855
|
+
: null,
|
|
1856
|
+
),
|
|
1617
1857
|
routeParamMap,
|
|
1618
1858
|
detectedBy: "static-scan-fastify",
|
|
1619
1859
|
});
|
|
@@ -1744,7 +1984,14 @@ function scanNestJsRoutes(cwd) {
|
|
|
1744
1984
|
method,
|
|
1745
1985
|
path: normalizedPath,
|
|
1746
1986
|
description: "",
|
|
1747
|
-
requestBody:
|
|
1987
|
+
requestBody: mergeBodyWithMultipart(
|
|
1988
|
+
bodyFields,
|
|
1989
|
+
needsBody
|
|
1990
|
+
? extractMultipartFieldsFromFile(
|
|
1991
|
+
sliceRouteSegment(content, match.index, methodPattern.source),
|
|
1992
|
+
)
|
|
1993
|
+
: null,
|
|
1994
|
+
),
|
|
1748
1995
|
routeParamMap,
|
|
1749
1996
|
detectedBy: "static-scan-nestjs",
|
|
1750
1997
|
});
|
|
@@ -1847,7 +2094,14 @@ function scanKoaRoutes(cwd) {
|
|
|
1847
2094
|
method,
|
|
1848
2095
|
path: routePath,
|
|
1849
2096
|
description: "",
|
|
1850
|
-
requestBody:
|
|
2097
|
+
requestBody: mergeBodyWithMultipart(
|
|
2098
|
+
bodyFields,
|
|
2099
|
+
needsBody
|
|
2100
|
+
? extractMultipartFieldsFromFile(
|
|
2101
|
+
sliceRouteSegment(content, match.index, routePattern.source),
|
|
2102
|
+
)
|
|
2103
|
+
: null,
|
|
2104
|
+
),
|
|
1851
2105
|
routeParamMap,
|
|
1852
2106
|
detectedBy: "static-scan-koa",
|
|
1853
2107
|
});
|
|
@@ -1947,7 +2201,14 @@ function scanHapiRoutes(cwd) {
|
|
|
1947
2201
|
method,
|
|
1948
2202
|
path: routePath,
|
|
1949
2203
|
description: "",
|
|
1950
|
-
requestBody:
|
|
2204
|
+
requestBody: mergeBodyWithMultipart(
|
|
2205
|
+
bodyFields,
|
|
2206
|
+
needsBody
|
|
2207
|
+
? extractMultipartFieldsFromFile(
|
|
2208
|
+
sliceRouteSegment(content, match.index, routePattern.source),
|
|
2209
|
+
)
|
|
2210
|
+
: null,
|
|
2211
|
+
),
|
|
1951
2212
|
routeParamMap,
|
|
1952
2213
|
detectedBy: "static-scan-hapi",
|
|
1953
2214
|
});
|
|
@@ -2048,7 +2309,14 @@ function scanAdonisRoutes(cwd) {
|
|
|
2048
2309
|
method,
|
|
2049
2310
|
path: routePath,
|
|
2050
2311
|
description: "",
|
|
2051
|
-
requestBody:
|
|
2312
|
+
requestBody: mergeBodyWithMultipart(
|
|
2313
|
+
bodyFields,
|
|
2314
|
+
needsBody
|
|
2315
|
+
? extractMultipartFieldsFromFile(
|
|
2316
|
+
sliceRouteSegment(content, match.index, routePattern.source),
|
|
2317
|
+
)
|
|
2318
|
+
: null,
|
|
2319
|
+
),
|
|
2052
2320
|
routeParamMap,
|
|
2053
2321
|
detectedBy: "static-scan-adonis",
|
|
2054
2322
|
});
|
|
@@ -2134,7 +2402,18 @@ function scanNuxtServerRoutes(cwd) {
|
|
|
2134
2402
|
method: m,
|
|
2135
2403
|
path: finalPath,
|
|
2136
2404
|
description: "",
|
|
2137
|
-
requestBody:
|
|
2405
|
+
requestBody:
|
|
2406
|
+
m !== "GET" && m !== "DELETE"
|
|
2407
|
+
? (function () {
|
|
2408
|
+
try {
|
|
2409
|
+
return extractMultipartFieldsFromFile(
|
|
2410
|
+
fs.readFileSync(fullPath, "utf8"),
|
|
2411
|
+
);
|
|
2412
|
+
} catch (e) {
|
|
2413
|
+
return null;
|
|
2414
|
+
}
|
|
2415
|
+
})()
|
|
2416
|
+
: null,
|
|
2138
2417
|
routeParamMap,
|
|
2139
2418
|
detectedBy: "static-scan-nuxt",
|
|
2140
2419
|
});
|
|
@@ -2223,7 +2502,14 @@ function scanSvelteKitServerRoutes(cwd) {
|
|
|
2223
2502
|
method,
|
|
2224
2503
|
path: routePath || "/",
|
|
2225
2504
|
description: "",
|
|
2226
|
-
requestBody:
|
|
2505
|
+
requestBody: mergeBodyWithMultipart(
|
|
2506
|
+
bodyFields,
|
|
2507
|
+
needsBody
|
|
2508
|
+
? extractMultipartFieldsFromFile(
|
|
2509
|
+
getMethodSegment(content, method, methods, "app") || content,
|
|
2510
|
+
)
|
|
2511
|
+
: null,
|
|
2512
|
+
),
|
|
2227
2513
|
routeParamMap,
|
|
2228
2514
|
detectedBy: "static-scan-sveltekit",
|
|
2229
2515
|
});
|
|
@@ -2340,7 +2626,10 @@ function scanRemixServerRoutes(cwd) {
|
|
|
2340
2626
|
if (seen.has(key)) return;
|
|
2341
2627
|
seen.add(key);
|
|
2342
2628
|
|
|
2343
|
-
const bodyFields =
|
|
2629
|
+
const bodyFields = mergeBodyWithMultipart(
|
|
2630
|
+
extractBodyFieldsFromFile(content),
|
|
2631
|
+
extractMultipartFieldsFromFile(content),
|
|
2632
|
+
);
|
|
2344
2633
|
endpoints.push({
|
|
2345
2634
|
method,
|
|
2346
2635
|
path: routePath,
|