Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs: fix cpSync crash on utf characters #54653

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 46 additions & 31 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -3130,22 +3130,24 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
ToNamespacedPath(env, &src);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemRead, src.ToStringView());
auto src_path = std::filesystem::path(src.ToStringView());

auto src_path = std::filesystem::path(src.ToU8StringView());

BufferValue dest(isolate, args[1]);
CHECK_NOT_NULL(*dest);
ToNamespacedPath(env, &dest);
THROW_IF_INSUFFICIENT_PERMISSIONS(
env, permission::PermissionScope::kFileSystemWrite, dest.ToStringView());
auto dest_path = std::filesystem::path(dest.ToStringView());

auto dest_path = std::filesystem::path(dest.ToU8StringView());
bool dereference = args[2]->IsTrue();
bool recursive = args[3]->IsTrue();

std::error_code error_code;
auto src_status = dereference
? std::filesystem::symlink_status(src_path, error_code)
: std::filesystem::status(src_path, error_code);

if (error_code) {
#ifdef _WIN32
int errorno = uv_translate_sys_error(error_code.value());
Expand All @@ -3169,34 +3171,41 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
if (!error_code) {
// Check if src and dest are identical.
if (std::filesystem::equivalent(src_path, dest_path)) {
std::string message =
"src and dest cannot be the same " + dest_path.string();
return THROW_ERR_FS_CP_EINVAL(env, message.c_str());
std::u8string message =
u8"src and dest cannot be the same " + dest_path.u8string();
return THROW_ERR_FS_CP_EINVAL(
env, reinterpret_cast<const char*>(message.c_str()));
}

const bool dest_is_dir =
dest_status.type() == std::filesystem::file_type::directory;

if (src_is_dir && !dest_is_dir) {
std::string message = "Cannot overwrite non-directory " +
src_path.string() + " with directory " +
dest_path.string();
return THROW_ERR_FS_CP_DIR_TO_NON_DIR(env, message.c_str());
std::u8string message = u8"Cannot overwrite non-directory " +
src_path.u8string() + u8" with directory " +
dest_path.u8string();
return THROW_ERR_FS_CP_DIR_TO_NON_DIR(
env, reinterpret_cast<const char*>(message.c_str()));
}

if (!src_is_dir && dest_is_dir) {
std::string message = "Cannot overwrite directory " + dest_path.string() +
" with non-directory " + src_path.string();
return THROW_ERR_FS_CP_NON_DIR_TO_DIR(env, message.c_str());
std::u8string message = u8"Cannot overwrite directory " +
dest_path.u8string() + u8" with non-directory " +
src_path.u8string();
return THROW_ERR_FS_CP_NON_DIR_TO_DIR(
env, reinterpret_cast<const char*>(message.c_str()));
}
}

std::string dest_path_str = dest_path.string();
std::u8string dest_path_str = dest_path.u8string();

// Check if dest_path is a subdirectory of src_path.
if (src_is_dir && dest_path_str.starts_with(src_path.string())) {
std::string message = "Cannot copy " + src_path.string() +
" to a subdirectory of self " + dest_path.string();
return THROW_ERR_FS_CP_EINVAL(env, message.c_str());
if (src_is_dir && dest_path_str.starts_with(src_path.u8string())) {
std::u8string message = u8"Cannot copy " + src_path.u8string() +
u8" to a subdirectory of self " +
dest_path.u8string();
return THROW_ERR_FS_CP_EINVAL(
env, reinterpret_cast<const char*>(message.c_str()));
}

auto dest_parent = dest_path.parent_path();
Expand All @@ -3207,9 +3216,11 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
dest_parent.parent_path() != dest_parent) {
if (std::filesystem::equivalent(
src_path, dest_path.parent_path(), error_code)) {
std::string message = "Cannot copy " + src_path.string() +
" to a subdirectory of self " + dest_path.string();
return THROW_ERR_FS_CP_EINVAL(env, message.c_str());
std::u8string message = u8"Cannot copy " + src_path.u8string() +
u8" to a subdirectory of self " +
dest_path.u8string();
return THROW_ERR_FS_CP_EINVAL(
env, reinterpret_cast<const char*>(message.c_str()));
}

// If equivalent fails, it's highly likely that dest_parent does not exist
Expand All @@ -3221,25 +3232,29 @@ static void CpSyncCheckPaths(const FunctionCallbackInfo<Value>& args) {
}

if (src_is_dir && !recursive) {
std::string message =
"Recursive option not enabled, cannot copy a directory: " +
src_path.string();
return THROW_ERR_FS_EISDIR(env, message.c_str());
std::u8string message =
u8"Recursive option not enabled, cannot copy a directory: " +
src_path.u8string();
return THROW_ERR_FS_EISDIR(env,
reinterpret_cast<const char*>(message.c_str()));
}

switch (src_status.type()) {
case std::filesystem::file_type::socket: {
std::string message = "Cannot copy a socket file: " + dest_path.string();
return THROW_ERR_FS_CP_SOCKET(env, message.c_str());
std::u8string message = u8"Cannot copy a socket file: " + dest_path_str;
return THROW_ERR_FS_CP_SOCKET(
env, reinterpret_cast<const char*>(message.c_str()));
}
case std::filesystem::file_type::fifo: {
std::string message = "Cannot copy a FIFO pipe: " + dest_path.string();
return THROW_ERR_FS_CP_FIFO_PIPE(env, message.c_str());
std::u8string message = u8"Cannot copy a FIFO pipe: " + dest_path_str;
return THROW_ERR_FS_CP_FIFO_PIPE(
env, reinterpret_cast<const char*>(message.c_str()));
}
case std::filesystem::file_type::unknown: {
std::string message =
"Cannot copy an unknown file type: " + dest_path.string();
return THROW_ERR_FS_CP_UNKNOWN(env, message.c_str());
std::u8string message =
u8"Cannot copy an unknown file type: " + dest_path_str;
return THROW_ERR_FS_CP_UNKNOWN(
env, reinterpret_cast<const char*>(message.c_str()));
}
default:
break;
Expand Down
4 changes: 4 additions & 0 deletions src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -562,6 +562,10 @@ class BufferValue : public MaybeStackBuffer<char> {
inline std::string_view ToStringView() const {
return std::string_view(out(), length());
}
inline std::u8string_view ToU8StringView() const {
return std::u8string_view(reinterpret_cast<const char8_t*>(out()),
length());
}
};

#define SPREAD_BUFFER_ARG(val, name) \
Expand Down
3 changes: 3 additions & 0 deletions test/fixtures/copy/utf/新建文件夹/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
purpose: 'testing copy'
};
8 changes: 8 additions & 0 deletions test/parallel/test-fs-cp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ function nextdir() {

// Synchronous implementation of copy.

// It copies a nested folder containing UTF characters.
{
const src = './test/fixtures/copy/utf/新建文件夹';
const dest = nextdir();
cpSync(src, dest, mustNotMutateObjectDeep({ recursive: true }));
assertDirEquivalent(src, dest);
}

// It copies a nested folder structure with files and folders.
{
const src = './test/fixtures/copy/kitchen-sink';
Expand Down
Loading