@opengis/fastify-table 2.5.46 → 2.5.48

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.
@@ -12,6 +12,8 @@ export interface IFile {
12
12
  fields?: Record<string, any>;
13
13
  error?: string;
14
14
  code?: number;
15
+ isValid?: boolean;
16
+ confidence?: string;
15
17
  }
16
18
  export declare function rotateFilenameFS(filename: string, dirpath: string): Promise<string>;
17
19
  export declare function rotateFilenameS3({ Name: filename, Bucket, Prefix, }: {
@@ -1 +1 @@
1
- {"version":3,"file":"uploadMultiPart.d.ts","sourceRoot":"","sources":["../../../../server/plugins/file/uploadMultiPart.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,KAAK;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AA2CD,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAkBvE;AAED,wBAAsB,gBAAgB,CACpC,EACE,IAAI,EAAE,QAAQ,EACd,MAA2C,EAC3C,MAAM,GACP,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACpD,MAAM,MAAW,mBA+BlB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,GAAG,EACR,EACE,UAAU,EACV,GAAG,EACH,MAAM,EACN,gBAAwB,GACzB,GAAE;IACD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACvB,GACL,OAAO,CAAC,KAAK,CAAC,CA0HhB;AAED,eAAe,eAAe,CAAC"}
1
+ {"version":3,"file":"uploadMultiPart.d.ts","sourceRoot":"","sources":["../../../../server/plugins/file/uploadMultiPart.ts"],"names":[],"mappings":"AA8BA,MAAM,WAAW,KAAK;IACpB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;IAC7B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AA2CD,wBAAsB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,mBAkBvE;AAED,wBAAsB,gBAAgB,CACpC,EACE,IAAI,EAAE,QAAQ,EACd,MAA2C,EAC3C,MAAM,GACP,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,EACpD,MAAM,MAAW,mBA+BlB;AAED,wBAAsB,eAAe,CACnC,GAAG,EAAE,GAAG,EACR,EACE,UAAU,EACV,GAAG,EACH,MAAM,EACN,gBAAwB,GACzB,GAAE;IACD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,GAAG,CAAC,EAAE,OAAO,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,OAAO,CAAC;CACvB,GACL,OAAO,CAAC,KAAK,CAAC,CA4HhB;AAED,eAAe,eAAe,CAAC"}
@@ -112,7 +112,7 @@ export async function uploadMultiPart(req, { extensions, raw, subdir, originalFi
112
112
  throw new BadRequestError("file buffer is empty");
113
113
  }
114
114
  const nameExt = ext.substring(1);
115
- const { isValid } = await inspectContent(fileBuffer, nameExt);
115
+ const { isValid, confidence } = await inspectContent(fileBuffer, nameExt);
116
116
  // return buffer for custom upload
117
117
  if (raw) {
118
118
  return {
@@ -121,6 +121,8 @@ export async function uploadMultiPart(req, { extensions, raw, subdir, originalFi
121
121
  extension: ext.substring(1),
122
122
  mimetype: value.mimetype,
123
123
  fields: value.fields,
124
+ isValid,
125
+ confidence,
124
126
  };
125
127
  }
126
128
  if (!isValid) {
@@ -1,6 +1,12 @@
1
- export default function inspectContent(buffer: Buffer, expectedExtension: string): Promise<{
1
+ export default function inspectContent(input: Buffer | ReadableStream, expectedExtension: string): Promise<{
2
2
  ext: any;
3
3
  mime: any;
4
4
  isValid: boolean;
5
+ confidence: any;
6
+ } | {
7
+ ext: string | undefined;
8
+ mime: string | undefined;
9
+ isValid: boolean;
10
+ confidence?: undefined;
5
11
  }>;
6
12
  //# sourceMappingURL=inspectContent.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"inspectContent.d.ts","sourceRoot":"","sources":["../../../../../server/plugins/file/utils/inspectContent.ts"],"names":[],"mappings":"AA+CA,wBAA8B,cAAc,CAC1C,MAAM,EAAE,MAAM,EACd,iBAAiB,EAAE,MAAM;;;;GAgD1B"}
1
+ {"version":3,"file":"inspectContent.d.ts","sourceRoot":"","sources":["../../../../../server/plugins/file/utils/inspectContent.ts"],"names":[],"mappings":"AAuGA,wBAA8B,cAAc,CAC1C,KAAK,EAAE,MAAM,GAAG,cAAc,EAC9B,iBAAiB,EAAE,MAAM;;;;;;;;;;GA4E1B"}
@@ -1,4 +1,4 @@
1
- import { fileTypeFromBuffer } from "file-type";
1
+ import { fileTypeFromBuffer, fileTypeFromStream } from "file-type";
2
2
  import { BadRequestError } from "../../../../errors.js";
3
3
  import applyHook from "../../hook/applyHook.js";
4
4
  // Office 97–2003 (.doc/.xls/.ppt) share CFB magic; file-type reports them as "cfb"
@@ -7,6 +7,25 @@ const cfbOfficeMimes = {
7
7
  xls: "application/vnd.ms-excel",
8
8
  ppt: "application/vnd.ms-powerpoint",
9
9
  };
10
+ function countDelimiter(line, delimiter) {
11
+ let count = 0;
12
+ let quoted = false;
13
+ for (let i = 0; i < line.length; i++) {
14
+ const char = line[i];
15
+ if (char === '"') {
16
+ if (quoted && line[i + 1] === '"') {
17
+ i++;
18
+ }
19
+ else {
20
+ quoted = !quoted;
21
+ }
22
+ }
23
+ else if (char === delimiter && !quoted) {
24
+ count++;
25
+ }
26
+ }
27
+ return count;
28
+ }
10
29
  const handlers = {
11
30
  pdf: (buffer) => {
12
31
  const head = buffer.subarray(0, 1024).toString("latin1");
@@ -14,6 +33,7 @@ const handlers = {
14
33
  return {
15
34
  ext: "pdf",
16
35
  mime: "application/pdf",
36
+ confidence: "heuristic",
17
37
  };
18
38
  }
19
39
  },
@@ -23,6 +43,7 @@ const handlers = {
23
43
  return {
24
44
  ext: "json",
25
45
  mime: "application/json",
46
+ confidence: "heuristic",
26
47
  };
27
48
  }
28
49
  },
@@ -34,30 +55,75 @@ const handlers = {
34
55
  return {
35
56
  ext: "geojson",
36
57
  mime: "application/geo+json",
58
+ confidence: "heuristic",
37
59
  };
38
60
  }
39
61
  },
62
+ csv: (buffer) => {
63
+ const text = buffer.subarray(0, 16 * 1024).toString("utf8");
64
+ const lines = text.split(/\r?\n/).filter((line) => line.trim());
65
+ if (lines.length < 2) {
66
+ return false;
67
+ }
68
+ const delimiters = [",", ";", "\t"];
69
+ return delimiters.reduce((acc, delimiter) => {
70
+ const counts = lines
71
+ .slice(0, 10)
72
+ .map((line) => countDelimiter(line, delimiter));
73
+ const first = counts[0];
74
+ if (first > 0 && counts.every((count) => count === first)) {
75
+ return {
76
+ ...acc,
77
+ ext: "csv",
78
+ mime: "text/csv",
79
+ confidence: "heuristic",
80
+ };
81
+ }
82
+ return acc;
83
+ }, {});
84
+ },
40
85
  };
41
- export default async function inspectContent(buffer, expectedExtension) {
86
+ const isReadableStream = (stream) => stream && stream instanceof ReadableStream;
87
+ export default async function inspectContent(input, expectedExtension) {
42
88
  if (!expectedExtension) {
43
- throw new BadRequestError("not enough params: expectedExtension");
89
+ throw new Error("not enough params: expectedExtension");
90
+ }
91
+ const buffer = Buffer.isBuffer(input) ? input : undefined;
92
+ const stream = !buffer ? input : undefined;
93
+ if (buffer && !buffer.length) {
94
+ throw BadRequestError("Invalid buffer");
95
+ }
96
+ if (stream && !isReadableStream(stream)) {
97
+ throw BadRequestError("invalid params: input");
44
98
  }
45
- if (!Buffer.isBuffer(buffer) || !buffer.length) {
46
- throw new BadRequestError("Invalid buffer");
99
+ if (!buffer && !stream) {
100
+ throw new Error("not enough params: input");
47
101
  }
48
102
  // custom inspection of extensions like kml, svg etc
49
103
  const hookData = (await applyHook("onInspectContent", {
50
104
  buffer,
105
+ stream,
51
106
  expectedExtension,
52
107
  }));
53
108
  if (hookData) {
54
109
  return hookData;
55
110
  }
56
- const { ext, mime } = (await fileTypeFromBuffer(buffer)) || {};
111
+ const inspectResult1 = buffer
112
+ ? await fileTypeFromBuffer(buffer)
113
+ : stream
114
+ ? await fileTypeFromStream(stream)
115
+ : undefined;
116
+ const inspectResult2 = stream ? await fileTypeFromStream(stream) : undefined;
117
+ const { ext, mime } = inspectResult1 || inspectResult2 || {};
57
118
  // ? geojson, signed pdf, kml, svg etc - not recognized - inspecting the content
58
- if (!ext && handlers[expectedExtension]) {
59
- const { ext: ext2, mime: mime2 } = (await handlers[expectedExtension](buffer)) || {};
60
- return { ext: ext2, mime: mime2, isValid: ext2 === expectedExtension };
119
+ if (!ext && handlers[expectedExtension] && buffer) {
120
+ const { ext: ext2, mime: mime2, confidence, } = (await handlers[expectedExtension](buffer)) || {};
121
+ return {
122
+ ext: ext2,
123
+ mime: mime2,
124
+ isValid: ext2 === expectedExtension,
125
+ confidence,
126
+ };
61
127
  }
62
128
  // ? .doc/.xls/.ppt are CFB containers — file-type returns "cfb", not the office ext
63
129
  if (ext === "cfb" && cfbOfficeMimes[expectedExtension]) {
@@ -1 +1 @@
1
- {"version":3,"file":"checkXSS.d.ts","sourceRoot":"","sources":["../../../../../server/plugins/policy/funcs/checkXSS.ts"],"names":[],"mappings":"AAGA,iBAAS,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAW,EAAE,EAAE,GAAG;;;;;;EAyD3C;AAED,eAAe,QAAQ,CAAC"}
1
+ {"version":3,"file":"checkXSS.d.ts","sourceRoot":"","sources":["../../../../../server/plugins/policy/funcs/checkXSS.ts"],"names":[],"mappings":"AAGA,iBAAS,QAAQ,CAAC,EAAE,IAAI,EAAE,MAAW,EAAE,EAAE,GAAG;;;;;;EA0D3C;AAED,eAAe,QAAQ,CAAC"}
@@ -14,6 +14,7 @@ function checkXSS({ body, schema = {} }) {
14
14
  "Tiny",
15
15
  "Ace",
16
16
  "Texteditor",
17
+ "RichText",
17
18
  ];
18
19
  Object.keys(body)
19
20
  .filter((key) => ["<", ">"].find((el) => body[key]?.includes?.(el)) &&
@@ -1 +1 @@
1
- {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/s3.ts"],"names":[],"mappings":"AAiGA,wBAAsB,sBAAsB,CAAC,EAAE,MAAW,iBAwCzD;AAED,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,GAAG,EACT,GAAG,SAAqB,iBAazB;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,GAAG,SAAqB,EACxB,EAAE,MAAW,iBAsBd;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,iBA2B9D;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,gBAQ7C;AAED,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,gBAMrD;AAED,wBAAsB,mBAAmB,CAAC,EAAE,MAAW,iBAyEtD;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,MAAW;;;;;;;;GAsCrE;AAKD,wBAAsB,WAAW,CAC/B,EACE,MAAM,EACN,GAAG,EACH,WAAW,EACX,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,EACD,EAAE,MAAW;;;;;;;;;;;;;;;;GAsDd;AASD,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB,iBAwBF;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,kBAY9C;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,iBAQjD;AAED,wBAAsB,WAAW,CAC/B,EACE,EAAE,EACF,KAAK,EACL,UAAU,GACX,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB,EACD,EAAE,MAAW;;;;;;;;GA+Bd;AAED,wBAAsB,YAAY,CAChC,EACE,EAAE,GACH,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;CACZ,EACD,EAAE,MAAW;;;;GA2Cd;AAED,wBAAsB,WAAW,CAC/B,EACE,EAAE,GACH,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;CACZ,EACD,EAAE,MAAW;;GAwBd;AAED,wBAAsB,eAAe,CACnC,EACE,EAAE,EACF,GAAG,EACH,MAAM,GACP,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,EACD,EAAE,MAAW;;;;;;;;;;;;;;GAqEd"}
1
+ {"version":3,"file":"s3.d.ts","sourceRoot":"","sources":["../../../../server/plugins/upload/s3.ts"],"names":[],"mappings":"AAmGA,wBAAsB,sBAAsB,CAAC,EAAE,MAAW,iBAwCzD;AAED,wBAAsB,cAAc,CAClC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,GAAG,EACT,GAAG,SAAqB,iBAazB;AAED,wBAAsB,gBAAgB,CACpC,EAAE,EAAE,MAAM,EACV,GAAG,SAAqB,EACxB,EAAE,MAAW,iBAsBd;AAED,wBAAsB,gBAAgB,CAAC,EAAE,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,MAAM,iBA2B9D;AAED,wBAAsB,aAAa,CAAC,EAAE,EAAE,MAAM,gBAQ7C;AAED,wBAAsB,oBAAoB,CAAC,GAAG,EAAE,MAAM,gBAMrD;AAED,wBAAsB,mBAAmB,CAAC,EAAE,MAAW,iBAyEtD;AAED,wBAAsB,UAAU,CAAC,EAAE,EAAE,EAAE,EAAE;IAAE,EAAE,EAAE,MAAM,CAAA;CAAE,EAAE,EAAE,MAAW;;;;;;;;GAsCrE;AAKD,wBAAsB,WAAW,CAC/B,EACE,MAAM,EACN,GAAG,EACH,WAAW,EACX,QAAQ,GACT,EAAE;IACD,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,EACD,EAAE,MAAW;;;;;;;;;;;;;;;;GAsDd;AASD,wBAAsB,QAAQ,CAC5B,EAAE,EAAE,MAAM,EACV,IAAI,EAAE;IACJ,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;CACpB,iBAwBF;AAED,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,kBAY9C;AAED,wBAAsB,iBAAiB,CAAC,EAAE,EAAE,MAAM,iBAQjD;AAED,wBAAsB,WAAW,CAC/B,EACE,EAAE,EACF,KAAK,EACL,UAAU,GACX,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,GAAG,CAAC;IACX,UAAU,EAAE,MAAM,CAAC;CACpB,EACD,EAAE,MAAW;;;;;;;;GAyCd;AAED,wBAAsB,YAAY,CAChC,EACE,EAAE,GACH,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;CACZ,EACD,EAAE,MAAW;;;;GA2Cd;AAED,wBAAsB,WAAW,CAC/B,EACE,EAAE,GACH,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;CACZ,EACD,EAAE,MAAW;;GAwBd;AAED,wBAAsB,eAAe,CACnC,EACE,EAAE,EACF,GAAG,EACH,MAAM,GACP,EAAE;IACD,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB,EACD,EAAE,MAAW;;;;;;;;;;;;;;GAqEd"}
@@ -4,6 +4,8 @@ import config from "../../../config.js";
4
4
  import s3Client from "../file/providers/s3/client.js";
5
5
  import rclient from "../redis/client.js";
6
6
  import logger from "../logger/getLogger.js";
7
+ import inspectContent from "../file/utils/inspectContent.js";
8
+ import { ConflictError } from "../../../errors.js";
7
9
  const uploads = new Map();
8
10
  const resumableUploads = new Map();
9
11
  // non-redis upload timeout handlers
@@ -339,6 +341,12 @@ export async function uploadChunk({ id, chunk, partNumber, }, s3 = s3Client) {
339
341
  if (upload.parts.some((p) => p.PartNumber === partNumber)) {
340
342
  throw new Error(`Part ${partNumber} already uploaded`);
341
343
  }
344
+ if (partNumber === 1) {
345
+ const { isValid } = await inspectContent(chunk, path.extname(upload.Key).substring(1));
346
+ if (!isValid) {
347
+ throw ConflictError("file type spoofing attempt detected");
348
+ }
349
+ }
342
350
  const res = await s3.send(new UploadPartCommand({
343
351
  Bucket: upload.Bucket,
344
352
  Key: upload.Key,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengis/fastify-table",
3
- "version": "2.5.46",
3
+ "version": "2.5.48",
4
4
  "type": "module",
5
5
  "description": "core-plugins",
6
6
  "keywords": [