@crawlee/memory-storage 3.17.0 → 3.17.1-beta.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/cache-helpers.js CHANGED
@@ -18,7 +18,7 @@ async function findOrCacheDatasetByPossibleId(client, entryNameOrId) {
18
18
  if (found) {
19
19
  return found;
20
20
  }
21
- const datasetDir = (0, node_path_1.resolve)(client.datasetsDirectory, entryNameOrId);
21
+ const datasetDir = (0, utils_1.resolveWithinDirectory)(client.datasetsDirectory, entryNameOrId);
22
22
  try {
23
23
  // Check if directory exists
24
24
  await (0, promises_1.access)(datasetDir);
@@ -99,7 +99,7 @@ async function findOrCacheKeyValueStoreByPossibleId(client, entryNameOrId) {
99
99
  if (found) {
100
100
  return found;
101
101
  }
102
- const keyValueStoreDir = (0, node_path_1.resolve)(client.keyValueStoresDirectory, entryNameOrId);
102
+ const keyValueStoreDir = (0, utils_1.resolveWithinDirectory)(client.keyValueStoresDirectory, entryNameOrId);
103
103
  try {
104
104
  // Check if directory exists
105
105
  await (0, promises_1.access)(keyValueStoreDir);
@@ -219,7 +219,7 @@ async function findRequestQueueByPossibleId(client, entryNameOrId) {
219
219
  if (found) {
220
220
  return found;
221
221
  }
222
- const requestQueueDir = (0, node_path_1.resolve)(client.requestQueuesDirectory, entryNameOrId);
222
+ const requestQueueDir = (0, utils_1.resolveWithinDirectory)(client.requestQueuesDirectory, entryNameOrId);
223
223
  try {
224
224
  // Check if directory exists
225
225
  await (0, promises_1.access)(requestQueueDir);
@@ -60,7 +60,7 @@ class KeyValueFileSystemEntry {
60
60
  catch {
61
61
  try {
62
62
  // Try without extension
63
- file = await (0, promises_1.readFile)((0, node_path_1.resolve)(this.storeDirectory, this.rawRecord.key));
63
+ file = await (0, promises_1.readFile)((0, utils_1.resolveWithinDirectory)(this.storeDirectory, this.rawRecord.key));
64
64
  utils_1.memoryStorageLog.warning([
65
65
  `Key-value entry "${this.rawRecord.key}" for store ${(0, win32_1.basename)(this.storeDirectory)} does not have a file extension, assuming it as text.`,
66
66
  'If you want to have correct interpretation of the file, you should add a file extension to the entry.',
@@ -88,8 +88,8 @@ class KeyValueFileSystemEntry {
88
88
  contentType && data.contentType && contentType.startsWith(data.contentType)
89
89
  ? data.key
90
90
  : `${data.key}.${data.extension}`;
91
- this.filePath ?? (this.filePath = (0, node_path_1.resolve)(this.storeDirectory, fileName));
92
- this.fileMetadataPath ?? (this.fileMetadataPath = (0, node_path_1.resolve)(this.storeDirectory, `${data.key}.__metadata__.json`));
91
+ this.filePath ?? (this.filePath = (0, utils_1.resolveWithinDirectory)(this.storeDirectory, fileName));
92
+ this.fileMetadataPath ?? (this.fileMetadataPath = (0, utils_1.resolveWithinDirectory)(this.storeDirectory, `${data.key}.__metadata__.json`));
93
93
  const { value, ...rest } = data;
94
94
  this.rawRecord = rest;
95
95
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@crawlee/memory-storage",
3
- "version": "3.17.0",
3
+ "version": "3.17.1-beta.0",
4
4
  "description": "A simple in-memory storage implementation of the Apify API",
5
5
  "engines": {
6
6
  "node": ">= 16"
@@ -49,7 +49,7 @@
49
49
  },
50
50
  "dependencies": {
51
51
  "@apify/log": "^2.4.0",
52
- "@crawlee/types": "3.17.0",
52
+ "@crawlee/types": "3.17.1-beta.0",
53
53
  "@sapphire/async-queue": "^1.5.0",
54
54
  "@sapphire/shapeshift": "^3.0.0",
55
55
  "content-type": "^1.0.4",
@@ -67,5 +67,5 @@
67
67
  }
68
68
  }
69
69
  },
70
- "gitHead": "8f2663aedfd2280ca35b9185b4e287f65a2ce969"
70
+ "gitHead": "d76458ce1dc2c433988ea09a14e222b56e29bf06"
71
71
  }
@@ -4,7 +4,6 @@ exports.DatasetClient = void 0;
4
4
  /* eslint-disable import/no-duplicates */
5
5
  const node_crypto_1 = require("node:crypto");
6
6
  const promises_1 = require("node:fs/promises");
7
- const node_path_1 = require("node:path");
8
7
  const shapeshift_1 = require("@sapphire/shapeshift");
9
8
  const fs_extra_1 = require("fs-extra");
10
9
  const background_handler_1 = require("../background-handler");
@@ -75,7 +74,7 @@ class DatasetClient extends base_client_1.BaseClient {
75
74
  value: void 0
76
75
  });
77
76
  this.name = options.name;
78
- this.datasetDirectory = (0, node_path_1.resolve)(options.baseStorageDirectory, this.name ?? this.id);
77
+ this.datasetDirectory = (0, utils_1.resolveWithinDirectory)(options.baseStorageDirectory, this.name ?? this.id);
79
78
  this.client = options.client;
80
79
  }
81
80
  async get() {
@@ -108,7 +107,7 @@ class DatasetClient extends base_client_1.BaseClient {
108
107
  }
109
108
  existingStoreById.name = parsed.name;
110
109
  const previousDir = existingStoreById.datasetDirectory;
111
- existingStoreById.datasetDirectory = (0, node_path_1.resolve)(this.client.datasetsDirectory, parsed.name ?? existingStoreById.name ?? existingStoreById.id);
110
+ existingStoreById.datasetDirectory = (0, utils_1.resolveWithinDirectory)(this.client.datasetsDirectory, parsed.name ?? existingStoreById.name ?? existingStoreById.id);
112
111
  await (0, fs_extra_1.move)(previousDir, existingStoreById.datasetDirectory, { overwrite: true });
113
112
  // Update timestamps
114
113
  existingStoreById.updateTimestamps(true);
@@ -4,7 +4,6 @@ exports.KeyValueStoreClient = void 0;
4
4
  const tslib_1 = require("tslib");
5
5
  const node_crypto_1 = require("node:crypto");
6
6
  const promises_1 = require("node:fs/promises");
7
- const node_path_1 = require("node:path");
8
7
  const node_stream_1 = require("node:stream");
9
8
  const shapeshift_1 = require("@sapphire/shapeshift");
10
9
  const fs_extra_1 = require("fs-extra");
@@ -65,7 +64,7 @@ class KeyValueStoreClient extends base_client_1.BaseClient {
65
64
  value: void 0
66
65
  });
67
66
  this.name = options.name;
68
- this.keyValueStoreDirectory = (0, node_path_1.resolve)(options.baseStorageDirectory, this.name ?? this.id);
67
+ this.keyValueStoreDirectory = (0, utils_1.resolveWithinDirectory)(options.baseStorageDirectory, this.name ?? this.id);
69
68
  this.client = options.client;
70
69
  }
71
70
  async get() {
@@ -98,7 +97,7 @@ class KeyValueStoreClient extends base_client_1.BaseClient {
98
97
  }
99
98
  existingStoreById.name = parsed.name;
100
99
  const previousDir = existingStoreById.keyValueStoreDirectory;
101
- existingStoreById.keyValueStoreDirectory = (0, node_path_1.resolve)(this.client.keyValueStoresDirectory, parsed.name ?? existingStoreById.name ?? existingStoreById.id);
100
+ existingStoreById.keyValueStoreDirectory = (0, utils_1.resolveWithinDirectory)(this.client.keyValueStoresDirectory, parsed.name ?? existingStoreById.name ?? existingStoreById.id);
102
101
  await (0, fs_extra_1.move)(previousDir, existingStoreById.keyValueStoreDirectory, { overwrite: true });
103
102
  // Update timestamps
104
103
  existingStoreById.updateTimestamps(true);
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.RequestQueueClient = void 0;
4
4
  const node_crypto_1 = require("node:crypto");
5
5
  const promises_1 = require("node:fs/promises");
6
- const node_path_1 = require("node:path");
7
6
  const async_queue_1 = require("@sapphire/async-queue");
8
7
  const shapeshift_1 = require("@sapphire/shapeshift");
9
8
  const fs_extra_1 = require("fs-extra");
@@ -96,7 +95,7 @@ class RequestQueueClient extends base_client_1.BaseClient {
96
95
  value: void 0
97
96
  });
98
97
  this.name = options.name;
99
- this.requestQueueDirectory = (0, node_path_1.resolve)(options.baseStorageDirectory, this.name ?? this.id);
98
+ this.requestQueueDirectory = (0, utils_1.resolveWithinDirectory)(options.baseStorageDirectory, this.name ?? this.id);
100
99
  this.client = options.client;
101
100
  }
102
101
  async getQueue() {
@@ -138,7 +137,7 @@ class RequestQueueClient extends base_client_1.BaseClient {
138
137
  }
139
138
  existingQueueById.name = parsed.name;
140
139
  const previousDir = existingQueueById.requestQueueDirectory;
141
- existingQueueById.requestQueueDirectory = (0, node_path_1.resolve)(this.client.requestQueuesDirectory, parsed.name ?? existingQueueById.name ?? existingQueueById.id);
140
+ existingQueueById.requestQueueDirectory = (0, utils_1.resolveWithinDirectory)(this.client.requestQueuesDirectory, parsed.name ?? existingQueueById.name ?? existingQueueById.id);
142
141
  await (0, fs_extra_1.move)(previousDir, existingQueueById.requestQueueDirectory, { overwrite: true });
143
142
  // Update timestamps
144
143
  existingQueueById.updateTimestamps(true);
package/utils.d.ts CHANGED
@@ -1,4 +1,10 @@
1
1
  import type * as storage from '@crawlee/types';
2
+ /**
3
+ * Resolves `segment` against `baseDirectory` and ensures the result stays within `baseDirectory`.
4
+ * Storage names and record keys are used as filesystem path components, so a value containing `..`
5
+ * or an absolute path could otherwise escape the intended directory.
6
+ */
7
+ export declare function resolveWithinDirectory(baseDirectory: string, segment: string): string;
2
8
  /**
3
9
  * Removes all properties with a null value
4
10
  * from the provided object.
package/utils.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.memoryStorageLog = void 0;
4
+ exports.resolveWithinDirectory = resolveWithinDirectory;
4
5
  exports.purgeNullsFromObject = purgeNullsFromObject;
5
6
  exports.uniqueKeyToRequestId = uniqueKeyToRequestId;
6
7
  exports.isBuffer = isBuffer;
@@ -12,9 +13,24 @@ exports.createPaginatedEntryList = createPaginatedEntryList;
12
13
  exports.createLazyIterablePromise = createLazyIterablePromise;
13
14
  const tslib_1 = require("tslib");
14
15
  const node_crypto_1 = require("node:crypto");
16
+ const node_path_1 = require("node:path");
15
17
  const shapeshift_1 = require("@sapphire/shapeshift");
16
18
  const log_1 = tslib_1.__importDefault(require("@apify/log"));
17
19
  const consts_1 = require("./consts");
20
+ /**
21
+ * Resolves `segment` against `baseDirectory` and ensures the result stays within `baseDirectory`.
22
+ * Storage names and record keys are used as filesystem path components, so a value containing `..`
23
+ * or an absolute path could otherwise escape the intended directory.
24
+ */
25
+ function resolveWithinDirectory(baseDirectory, segment) {
26
+ const base = (0, node_path_1.resolve)(baseDirectory);
27
+ const resolved = (0, node_path_1.resolve)(base, segment);
28
+ if (resolved !== base && !resolved.startsWith(`${base}${node_path_1.sep}`)) {
29
+ throw new Error(`"${segment}" is not allowed because it would resolve outside of the storage directory. ` +
30
+ `Storage names and record keys must not contain path traversal segments ("..") or absolute paths.`);
31
+ }
32
+ return resolved;
33
+ }
18
34
  /**
19
35
  * Removes all properties with a null value
20
36
  * from the provided object.