@oino-ts/blob 1.0.4 → 1.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oino-ts/blob",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "OINO TS library package for publishing blob storage as a REST API.",
5
5
  "author": "Matias Kiviniemi (pragmatta)",
6
6
  "license": "MPL-2.0",
@@ -19,13 +19,13 @@
19
19
  "module": "./dist/esm/index.js",
20
20
  "types": "./dist/types/index.d.ts",
21
21
  "dependencies": {
22
- "@oino-ts/common": "1.0.4",
22
+ "@oino-ts/common": "1.0.6",
23
23
  "oino-ts": "file:.."
24
24
  },
25
25
  "devDependencies": {
26
- "@oino-ts/types": "1.0.4",
26
+ "@oino-ts/types": "1.0.6",
27
27
  "@types/bun": "^1.1.14",
28
- "@types/node": "^21.0.40",
28
+ "@types/node": "^21.0.60",
29
29
  "typescript": "~5.9.0"
30
30
  },
31
31
  "files": [
package/src/OINOBlob.ts CHANGED
@@ -11,6 +11,8 @@ const BLOB_LIKE_ESCAPE_REGEX = /[.*+?^${}()|[\]\\]/g
11
11
  const BLOB_LIKE_PERCENT_REGEX = /%/g
12
12
  const BLOB_LIKE_UNDERSCORE_REGEX = /_/g
13
13
 
14
+ const BLOB_SANITIZE_DEFAULT_REGEX = /[\x00-\x1f\x7f]/g
15
+
14
16
  /**
15
17
  * Abstract base class for blob storage backends. Subclasses implement
16
18
  * the two core operations (`listEntries` and `fetchEntry`) for a specific
@@ -62,6 +64,22 @@ export abstract class OINOBlob extends OINODataSource {
62
64
  return v
63
65
  }
64
66
 
67
+ // ── Blob name sanitization ──────────────────────────────────────────────
68
+
69
+ /**
70
+ * Sanitize a blob name by replacing characters that are illegal or unsafe
71
+ * on this storage backend with `_`.
72
+ *
73
+ * The base implementation strips ASCII control characters (U+0000–U+001F
74
+ * and U+007F). Subclasses should override to apply additional
75
+ * platform-specific rules.
76
+ *
77
+ * @param name raw blob name (path within the container)
78
+ */
79
+ sanitizeName(name: string): string {
80
+ return name.replace(BLOB_SANITIZE_DEFAULT_REGEX, "_")
81
+ }
82
+
65
83
  // ── Blob-specific filter helper ───────────────────────────────────────
66
84
 
67
85
  /**
@@ -286,6 +286,7 @@ export async function OINOTestBlob(storageParams: OINOBlobStorageParams, testPar
286
286
  expect(verify_result.success).toBe(true)
287
287
  expect(verify_result.blobData).toBeDefined()
288
288
  expect(new TextDecoder().decode(verify_result.blobData)).toBe(new TextDecoder().decode(testParams.uploadContent))
289
+ expect(verify_result.blobDataType).toBe(testParams.uploadContentType)
289
290
  })
290
291
 
291
292
  // ── UPDATE (PUT) ──────────────────────────────────────────────────────
@@ -326,6 +327,7 @@ export async function OINOTestBlob(storageParams: OINOBlobStorageParams, testPar
326
327
  const verify_result: OINOBlobApiResult = await api.doApiRequest(verify_request)
327
328
  expect(verify_result.success).toBe(true)
328
329
  expect(new TextDecoder().decode(verify_result.blobData)).toBe(new TextDecoder().decode(testParams.updateContent))
330
+ expect(verify_result.blobDataType).toBe(testParams.uploadContentType)
329
331
  })
330
332
 
331
333
  // ── DELETE ────────────────────────────────────────────────────────────
@@ -126,7 +126,7 @@ export class OINOBlobApi extends OINOApi {
126
126
  } else {
127
127
  // ── Download blob ────────────────────────────────────────────
128
128
  try {
129
- const name = decodeURIComponent(request.rowId)
129
+ const name = this.blob.sanitizeName(decodeURIComponent(request.rowId))
130
130
  const fetch_result = await this.blob.fetchEntry(name)
131
131
  result.blobData = fetch_result.content
132
132
  result.blobDataType = fetch_result.contentType
@@ -142,7 +142,7 @@ export class OINOBlobApi extends OINOApi {
142
142
  result.setError(400, "HTTP " + request.method + " method requires an URL ID (blob name)!", "DoRequest")
143
143
  } else {
144
144
  try {
145
- const name = decodeURIComponent(request.rowId)
145
+ const name = this.blob.sanitizeName(decodeURIComponent(request.rowId))
146
146
  const content_type = request.headers.get("content-type") ?? "application/octet-stream"
147
147
  const data = request.rowData
148
148
  const content: Uint8Array = data instanceof Uint8Array ? data : request.bodyAsBuffer()
@@ -159,7 +159,7 @@ export class OINOBlobApi extends OINOApi {
159
159
  result.setError(400, "HTTP DELETE method requires an URL ID (blob name)!", "DoRequest")
160
160
  } else {
161
161
  try {
162
- const name = decodeURIComponent(request.rowId)
162
+ const name = this.blob.sanitizeName(decodeURIComponent(request.rowId))
163
163
  await this.blob.deleteEntry(name)
164
164
  } catch (e: any) {
165
165
  result.setError(500, "Error deleting blob: " + e.message, "DoDelete")