Skip to content

Commit f8b421e

Browse files
LiviaMedeirosRafaelGSS
authored andcommitted
fs: make mkdtemp accept buffers and URL
PR-URL: #48828 Reviewed-By: Mohammed Keyvanzadeh <mohammadkeyvanzade94@gmail.com> Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com>
1 parent b206233 commit f8b421e

File tree

5 files changed

+137
-35
lines changed

5 files changed

+137
-35
lines changed

doc/api/fs.md

+12-3
Original file line numberDiff line numberDiff line change
@@ -1159,14 +1159,17 @@ makeDirectory().catch(console.error);
11591159
<!-- YAML
11601160
added: v10.0.0
11611161
changes:
1162+
- version: REPLACEME
1163+
pr-url: https://github.com/nodejs/node/pull/48828
1164+
description: The `prefix` parameter now accepts buffers and URL.
11621165
- version:
11631166
- v16.5.0
11641167
- v14.18.0
11651168
pr-url: https://github.com/nodejs/node/pull/39028
11661169
description: The `prefix` parameter now accepts an empty string.
11671170
-->
11681171
1169-
* `prefix` {string}
1172+
* `prefix` {string|Buffer|URL}
11701173
* `options` {string|Object}
11711174
* `encoding` {string} **Default:** `'utf8'`
11721175
* Returns: {Promise} Fulfills with a string containing the file system path
@@ -3244,6 +3247,9 @@ See the POSIX mkdir(2) documentation for more details.
32443247
<!-- YAML
32453248
added: v5.10.0
32463249
changes:
3250+
- version: REPLACEME
3251+
pr-url: https://github.com/nodejs/node/pull/48828
3252+
description: The `prefix` parameter now accepts buffers and URL.
32473253
- version: v18.0.0
32483254
pr-url: https://github.com/nodejs/node/pull/41678
32493255
description: Passing an invalid callback to the `callback` argument
@@ -3267,7 +3273,7 @@ changes:
32673273
description: The `callback` parameter is optional now.
32683274
-->
32693275
3270-
* `prefix` {string}
3276+
* `prefix` {string|Buffer|URL}
32713277
* `options` {string|Object}
32723278
* `encoding` {string} **Default:** `'utf8'`
32733279
* `callback` {Function}
@@ -5550,14 +5556,17 @@ See the POSIX mkdir(2) documentation for more details.
55505556
<!-- YAML
55515557
added: v5.10.0
55525558
changes:
5559+
- version: REPLACEME
5560+
pr-url: https://github.com/nodejs/node/pull/48828
5561+
description: The `prefix` parameter now accepts buffers and URL.
55535562
- version:
55545563
- v16.5.0
55555564
- v14.18.0
55565565
pr-url: https://github.com/nodejs/node/pull/39028
55575566
description: The `prefix` parameter now accepts an empty string.
55585567
-->
55595568
5560-
* `prefix` {string}
5569+
* `prefix` {string|Buffer|URL}
55615570
* `options` {string|Object}
55625571
* `encoding` {string} **Default:** `'utf8'`
55635572
* Returns: {string}

lib/fs.js

+19-9
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,6 @@ const {
105105
getValidatedPath,
106106
getValidMode,
107107
handleErrorFromBinding,
108-
nullCheck,
109108
possiblyTransformPath,
110109
preprocessSymlinkDestination,
111110
Stats,
@@ -2900,7 +2899,7 @@ realpath.native = (path, options, callback) => {
29002899

29012900
/**
29022901
* Creates a unique temporary directory.
2903-
* @param {string} prefix
2902+
* @param {string | Buffer | URL} prefix
29042903
* @param {string | { encoding?: string; }} [options]
29052904
* @param {(
29062905
* err?: Error,
@@ -2912,29 +2911,40 @@ function mkdtemp(prefix, options, callback) {
29122911
callback = makeCallback(typeof options === 'function' ? options : callback);
29132912
options = getOptions(options);
29142913

2915-
validateString(prefix, 'prefix');
2916-
nullCheck(prefix, 'prefix');
29172914
prefix = getValidatedPath(prefix, 'prefix');
29182915
warnOnNonPortableTemplate(prefix);
2916+
2917+
let path;
2918+
if (typeof prefix === 'string') {
2919+
path = `${prefix}XXXXXX`;
2920+
} else {
2921+
path = Buffer.concat([prefix, Buffer.from('XXXXXX')]);
2922+
}
2923+
29192924
const req = new FSReqCallback();
29202925
req.oncomplete = callback;
2921-
binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, req);
2926+
binding.mkdtemp(path, options.encoding, req);
29222927
}
29232928

29242929
/**
29252930
* Synchronously creates a unique temporary directory.
2926-
* @param {string} prefix
2931+
* @param {string | Buffer | URL} prefix
29272932
* @param {string | { encoding?: string; }} [options]
29282933
* @returns {string}
29292934
*/
29302935
function mkdtempSync(prefix, options) {
29312936
options = getOptions(options);
29322937

2933-
validateString(prefix, 'prefix');
2934-
nullCheck(prefix, 'prefix');
29352938
prefix = getValidatedPath(prefix, 'prefix');
29362939
warnOnNonPortableTemplate(prefix);
2937-
const path = `${prefix}XXXXXX`;
2940+
2941+
let path;
2942+
if (typeof prefix === 'string') {
2943+
path = `${prefix}XXXXXX`;
2944+
} else {
2945+
path = Buffer.concat([prefix, Buffer.from('XXXXXX')]);
2946+
}
2947+
29382948
const ctx = { path };
29392949
const result = binding.mkdtemp(path, options.encoding,
29402950
undefined, ctx);

lib/internal/fs/promises.js

+10-4
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ const {
5959
getStatsFromBinding,
6060
getValidatedPath,
6161
getValidMode,
62-
nullCheck,
6362
preprocessSymlinkDestination,
6463
stringToFlags,
6564
stringToSymlinkType,
@@ -996,10 +995,17 @@ async function realpath(path, options) {
996995
async function mkdtemp(prefix, options) {
997996
options = getOptions(options);
998997

999-
validateString(prefix, 'prefix');
1000-
nullCheck(prefix);
998+
prefix = getValidatedPath(prefix, 'prefix');
1001999
warnOnNonPortableTemplate(prefix);
1002-
return binding.mkdtemp(`${prefix}XXXXXX`, options.encoding, kUsePromises);
1000+
1001+
let path;
1002+
if (typeof prefix === 'string') {
1003+
path = `${prefix}XXXXXX`;
1004+
} else {
1005+
path = Buffer.concat([prefix, Buffer.from('XXXXXX')]);
1006+
}
1007+
1008+
return binding.mkdtemp(path, options.encoding, kUsePromises);
10031009
}
10041010

10051011
async function writeFile(path, data, options) {

lib/internal/fs/utils.js

+4-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const {
2020
StringPrototypeEndsWith,
2121
StringPrototypeIncludes,
2222
Symbol,
23+
TypedArrayPrototypeAt,
2324
TypedArrayPrototypeIncludes,
2425
} = primordials;
2526

@@ -751,7 +752,9 @@ let nonPortableTemplateWarn = true;
751752
function warnOnNonPortableTemplate(template) {
752753
// Template strings passed to the mkdtemp() family of functions should not
753754
// end with 'X' because they are handled inconsistently across platforms.
754-
if (nonPortableTemplateWarn && StringPrototypeEndsWith(template, 'X')) {
755+
if (nonPortableTemplateWarn &&
756+
((typeof template === 'string' && StringPrototypeEndsWith(template, 'X')) ||
757+
(typeof template !== 'string' && TypedArrayPrototypeAt(template, -1) === 0x58))) {
755758
process.emitWarning('mkdtemp() templates ending with X are not portable. ' +
756759
'For details see: https://nodejs.org/api/fs.html');
757760
nonPortableTemplateWarn = false;

test/parallel/test-fs-mkdtemp.js

+92-18
Original file line numberDiff line numberDiff line change
@@ -8,29 +8,103 @@ const path = require('path');
88
const tmpdir = require('../common/tmpdir');
99
tmpdir.refresh();
1010

11-
const tmpFolder = fs.mkdtempSync(path.join(tmpdir.path, 'foo.'));
12-
13-
assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length);
14-
assert(fs.existsSync(tmpFolder));
15-
16-
const utf8 = fs.mkdtempSync(path.join(tmpdir.path, '\u0222abc.'));
17-
assert.strictEqual(Buffer.byteLength(path.basename(utf8)),
18-
Buffer.byteLength('\u0222abc.XXXXXX'));
19-
assert(fs.existsSync(utf8));
20-
2111
function handler(err, folder) {
2212
assert.ifError(err);
2313
assert(fs.existsSync(folder));
2414
assert.strictEqual(this, undefined);
2515
}
2616

27-
fs.mkdtemp(path.join(tmpdir.path, 'bar.'), common.mustCall(handler));
17+
// Test with plain string
18+
{
19+
const tmpFolder = fs.mkdtempSync(path.join(tmpdir.path, 'foo.'));
20+
21+
assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length);
22+
assert(fs.existsSync(tmpFolder));
23+
24+
const utf8 = fs.mkdtempSync(path.join(tmpdir.path, '\u0222abc.'));
25+
assert.strictEqual(Buffer.byteLength(path.basename(utf8)),
26+
Buffer.byteLength('\u0222abc.XXXXXX'));
27+
assert(fs.existsSync(utf8));
28+
29+
fs.mkdtemp(path.join(tmpdir.path, 'bar.'), common.mustCall(handler));
30+
31+
// Same test as above, but making sure that passing an options object doesn't
32+
// affect the way the callback function is handled.
33+
fs.mkdtemp(path.join(tmpdir.path, 'bar.'), {}, common.mustCall(handler));
34+
35+
const warningMsg = 'mkdtemp() templates ending with X are not portable. ' +
36+
'For details see: https://nodejs.org/api/fs.html';
37+
common.expectWarning('Warning', warningMsg);
38+
fs.mkdtemp(path.join(tmpdir.path, 'bar.X'), common.mustCall(handler));
39+
}
40+
41+
// Test with URL object
42+
{
43+
tmpdir.url = new URL(`file://${tmpdir.path}`);
44+
const urljoin = (base, path) => new URL(path, base);
45+
46+
const tmpFolder = fs.mkdtempSync(urljoin(tmpdir.url, 'foo.'));
47+
48+
assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length);
49+
assert(fs.existsSync(tmpFolder));
50+
51+
const utf8 = fs.mkdtempSync(urljoin(tmpdir.url, '\u0222abc.'));
52+
assert.strictEqual(Buffer.byteLength(path.basename(utf8)),
53+
Buffer.byteLength('\u0222abc.XXXXXX'));
54+
assert(fs.existsSync(utf8));
55+
56+
fs.mkdtemp(urljoin(tmpdir.url, 'bar.'), common.mustCall(handler));
57+
58+
// Same test as above, but making sure that passing an options object doesn't
59+
// affect the way the callback function is handled.
60+
fs.mkdtemp(urljoin(tmpdir.url, 'bar.'), {}, common.mustCall(handler));
61+
62+
// Warning fires only once
63+
fs.mkdtemp(urljoin(tmpdir.url, 'bar.X'), common.mustCall(handler));
64+
}
65+
66+
// Test with Buffer
67+
{
68+
const tmpFolder = fs.mkdtempSync(Buffer.from(path.join(tmpdir.path, 'foo.')));
69+
70+
assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length);
71+
assert(fs.existsSync(tmpFolder));
2872

29-
// Same test as above, but making sure that passing an options object doesn't
30-
// affect the way the callback function is handled.
31-
fs.mkdtemp(path.join(tmpdir.path, 'bar.'), {}, common.mustCall(handler));
73+
const utf8 = fs.mkdtempSync(Buffer.from(path.join(tmpdir.path, '\u0222abc.')));
74+
assert.strictEqual(Buffer.byteLength(path.basename(utf8)),
75+
Buffer.byteLength('\u0222abc.XXXXXX'));
76+
assert(fs.existsSync(utf8));
77+
78+
fs.mkdtemp(Buffer.from(path.join(tmpdir.path, 'bar.')), common.mustCall(handler));
79+
80+
// Same test as above, but making sure that passing an options object doesn't
81+
// affect the way the callback function is handled.
82+
fs.mkdtemp(Buffer.from(path.join(tmpdir.path, 'bar.')), {}, common.mustCall(handler));
83+
84+
// Warning fires only once
85+
fs.mkdtemp(Buffer.from(path.join(tmpdir.path, 'bar.X')), common.mustCall(handler));
86+
}
3287

33-
const warningMsg = 'mkdtemp() templates ending with X are not portable. ' +
34-
'For details see: https://nodejs.org/api/fs.html';
35-
common.expectWarning('Warning', warningMsg);
36-
fs.mkdtemp(path.join(tmpdir.path, 'bar.X'), common.mustCall(handler));
88+
// Test with Uint8Array
89+
{
90+
const encoder = new TextEncoder();
91+
92+
const tmpFolder = fs.mkdtempSync(encoder.encode(path.join(tmpdir.path, 'foo.')));
93+
94+
assert.strictEqual(path.basename(tmpFolder).length, 'foo.XXXXXX'.length);
95+
assert(fs.existsSync(tmpFolder));
96+
97+
const utf8 = fs.mkdtempSync(encoder.encode(path.join(tmpdir.path, '\u0222abc.')));
98+
assert.strictEqual(Buffer.byteLength(path.basename(utf8)),
99+
Buffer.byteLength('\u0222abc.XXXXXX'));
100+
assert(fs.existsSync(utf8));
101+
102+
fs.mkdtemp(encoder.encode(path.join(tmpdir.path, 'bar.')), common.mustCall(handler));
103+
104+
// Same test as above, but making sure that passing an options object doesn't
105+
// affect the way the callback function is handled.
106+
fs.mkdtemp(encoder.encode(path.join(tmpdir.path, 'bar.')), {}, common.mustCall(handler));
107+
108+
// Warning fires only once
109+
fs.mkdtemp(encoder.encode(path.join(tmpdir.path, 'bar.X')), common.mustCall(handler));
110+
}

0 commit comments

Comments
 (0)