Skip to content

Commit 0fcda9b

Browse files
cursoragentanonrig
andcommitted
fs: validate copyFile paths in the existing C++ binding
Fold getValidatedPath, file URL conversion, and NUL checks into binding.copyFile so the sync, callback, and promises paths share one implementation. Remove the extra copyFileSync binding. Signed-off-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Yagiz Nizipli <anonrig@users.noreply.github.com>
1 parent e4dc3a1 commit 0fcda9b

5 files changed

Lines changed: 24 additions & 56 deletions

File tree

lib/fs.js

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3659,8 +3659,6 @@ function copyFile(src, dest, mode, callback) {
36593659
const h = vfsState.handlers;
36603660
if (h !== null && vfsVoid(h.copyFile(src, dest, mode), callback)) return;
36613661

3662-
src = getValidatedPath(src, 'src');
3663-
dest = getValidatedPath(dest, 'dest');
36643662
callback = makeCallback(callback);
36653663

36663664
const req = new FSReqCallback();
@@ -3682,7 +3680,7 @@ function copyFileSync(src, dest, mode) {
36823680
const result = h.copyFileSync(src, dest, mode);
36833681
if (result !== undefined) return;
36843682
}
3685-
binding.copyFileSync(src, dest, mode);
3683+
binding.copyFile(src, dest, mode);
36863684
}
36873685

36883686
/**

lib/internal/fs/promises.js

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,12 +1335,7 @@ async function copyFile(src, dest, mode) {
13351335
if (promise !== undefined) { await promise; return; }
13361336
}
13371337
return await PromisePrototypeThen(
1338-
binding.copyFile(
1339-
getValidatedPath(src, 'src'),
1340-
getValidatedPath(dest, 'dest'),
1341-
mode,
1342-
kUsePromises,
1343-
),
1338+
binding.copyFile(src, dest, mode, kUsePromises),
13441339
undefined,
13451340
handleErrorFromBinding,
13461341
);

src/node_file.cc

Lines changed: 10 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -2464,9 +2464,10 @@ static bool ContainsNul(const BufferValue& path) {
24642464
std::memchr(path.out(), '\0', path.length()) != nullptr;
24652465
}
24662466

2467-
// C++ equivalent of getValidatedPath(value, propName): accepts string,
2468-
// Uint8Array/Buffer, or WHATWG URL, rejects embedded NUL bytes, and converts
2469-
// file: URLs to paths. Returns an empty MaybeLocal and throws on failure.
2467+
// C++ equivalent of getValidatedPath(value, propName), used by CopyFile for
2468+
// the sync, callback, and promises paths. Accepts string, Uint8Array/Buffer,
2469+
// or WHATWG URL, rejects embedded NUL bytes, and converts file: URLs to
2470+
// paths. Returns an empty MaybeLocal and throws on failure.
24702471
static MaybeLocal<Value> GetValidatedPath(Environment* env,
24712472
Local<Value> input,
24722473
const char* prop_name) {
@@ -2536,13 +2537,15 @@ static MaybeLocal<Value> GetValidatedPath(Environment* env,
25362537
return MaybeLocal<Value>();
25372538
}
25382539

2539-
// Full C++ implementation of fs.copyFileSync(): path validation, mode
2540-
// validation, permission checks, and the copy itself.
2541-
static void CopyFileSync(const FunctionCallbackInfo<Value>& args) {
2540+
// Shared by the sync, callback, and promises copyFile paths. Path validation
2541+
// (string / Uint8Array / file: URL, NUL checks) happens here so JS callers
2542+
// can pass the original arguments through.
2543+
static void CopyFile(const FunctionCallbackInfo<Value>& args) {
25422544
Environment* env = Environment::GetCurrent(args);
25432545
Isolate* isolate = env->isolate();
25442546

2545-
CHECK_GE(args.Length(), 2); // src, dest[, mode]
2547+
const int argc = args.Length();
2548+
CHECK_GE(argc, 3); // src, dest, flags[, req]
25462549

25472550
Local<Value> src_val;
25482551
if (!GetValidatedPath(env, args[0], "src").ToLocal(&src_val)) {
@@ -2575,45 +2578,12 @@ static void CopyFileSync(const FunctionCallbackInfo<Value>& args) {
25752578
return;
25762579
}
25772580

2578-
Local<Value> mode = args.Length() > 2 ? args[2] : Undefined(isolate);
2579-
int flags;
2580-
if (!GetValidFileMode(env, mode, UV_FS_COPYFILE).To(&flags)) {
2581-
return;
2582-
}
2583-
2584-
ToNamespacedPath(env, &src);
2585-
ToNamespacedPath(env, &dest);
2586-
2587-
THROW_IF_INSUFFICIENT_PERMISSIONS(
2588-
env, permission::PermissionScope::kFileSystemRead, src.ToStringView());
2589-
THROW_IF_INSUFFICIENT_PERMISSIONS(
2590-
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());
2591-
2592-
FSReqWrapSync req_wrap_sync("copyfile", *src, *dest);
2593-
FS_SYNC_TRACE_BEGIN(copyfile);
2594-
SyncCallAndThrowOnError(
2595-
env, &req_wrap_sync, uv_fs_copyfile, *src, *dest, flags);
2596-
FS_SYNC_TRACE_END(copyfile);
2597-
}
2598-
2599-
static void CopyFile(const FunctionCallbackInfo<Value>& args) {
2600-
Environment* env = Environment::GetCurrent(args);
2601-
Isolate* isolate = env->isolate();
2602-
2603-
const int argc = args.Length();
2604-
CHECK_GE(argc, 3); // src, dest, flags
2605-
26062581
int flags;
26072582
if (!GetValidFileMode(env, args[2], UV_FS_COPYFILE).To(&flags)) {
26082583
return;
26092584
}
26102585

2611-
BufferValue src(isolate, args[0]);
2612-
CHECK_NOT_NULL(*src);
26132586
ToNamespacedPath(env, &src);
2614-
2615-
BufferValue dest(isolate, args[1]);
2616-
CHECK_NOT_NULL(*dest);
26172587
ToNamespacedPath(env, &dest);
26182588

26192589
if (argc > 3) { // copyFile(src, dest, flags, req)
@@ -4409,7 +4379,6 @@ static void CreatePerIsolateProperties(IsolateData* isolate_data,
44094379
SetMethod(isolate, target, "writeFileUtf8", WriteFileUtf8);
44104380
SetMethod(isolate, target, "realpath", RealPath);
44114381
SetMethod(isolate, target, "copyFile", CopyFile);
4412-
SetMethod(isolate, target, "copyFileSync", CopyFileSync);
44134382

44144383
SetMethod(isolate, target, "chmod", Chmod);
44154384
SetMethod(isolate, target, "fchmod", FChmod);
@@ -4538,7 +4507,6 @@ void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
45384507
registry->Register(WriteFileUtf8);
45394508
registry->Register(RealPath);
45404509
registry->Register(CopyFile);
4541-
registry->Register(CopyFileSync);
45424510

45434511
registry->Register(CpSyncCheckPaths);
45444512
registry->Register(CpSyncOverrideFile);

test/parallel/test-fs-copyfile.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,14 @@ assert.throws(() => {
167167
message: 'The URL must be of scheme file',
168168
});
169169

170+
assert.throws(() => {
171+
fs.copyFile(new URL('http://example.com/a'), dest, common.mustNotCall());
172+
}, {
173+
code: 'ERR_INVALID_URL_SCHEME',
174+
name: 'TypeError',
175+
message: 'The URL must be of scheme file',
176+
});
177+
170178
if (common.isWindows) {
171179
['%2f', '%2F', '%5c', '%5C'].forEach((i) => {
172180
assert.throws(

typings/internalBinding/fs.d.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ declare namespace InternalFSBinding {
7272
function close(fd: number, req: FSReqCallback): void;
7373
function close(fd: number): void;
7474

75-
function copyFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, req: FSReqCallback): void;
76-
function copyFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, req: undefined, ctx: FSSyncContext): void;
77-
function copyFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, usePromises: typeof kUsePromises): Promise<void>;
78-
function copyFileSync(src: unknown, dest: unknown, mode?: unknown): void;
75+
function copyFile(src: unknown, dest: unknown, mode?: unknown): void;
76+
function copyFile(src: unknown, dest: unknown, mode: unknown, req: FSReqCallback): void;
77+
function copyFile(src: unknown, dest: unknown, mode: unknown, req: undefined, ctx: FSSyncContext): void;
78+
function copyFile(src: unknown, dest: unknown, mode: unknown, usePromises: typeof kUsePromises): Promise<void>;
7979

8080
function cpSyncCheckPaths(src: StringOrBuffer, dest: StringOrBuffer, dereference: boolean, recursive: boolean): void;
8181
function cpSyncOverrideFile(src: StringOrBuffer, dest: StringOrBuffer, mode: number, preserveTimestamps: boolean): void;
@@ -262,7 +262,6 @@ export interface FsBinding {
262262
chown: typeof InternalFSBinding.chown;
263263
close: typeof InternalFSBinding.close;
264264
copyFile: typeof InternalFSBinding.copyFile;
265-
copyFileSync: typeof InternalFSBinding.copyFileSync;
266265
cpSyncCheckPaths: typeof InternalFSBinding.cpSyncCheckPaths;
267266
cpSyncOverrideFile: typeof InternalFSBinding.cpSyncOverrideFile;
268267
cpSyncCopyDir: typeof InternalFSBinding.cpSyncCopyDir;

0 commit comments

Comments
 (0)