Skip to content

Commit

Permalink
fs,url: move FileURLToPath to node_url
Browse files Browse the repository at this point in the history
PR-URL: #50090
Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
Reviewed-By: Vinícius Lourenço Claro Cardoso <contact@viniciusl.com.br>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Stephen Belanger <admin@stephenbelanger.com>
  • Loading branch information
anonrig authored and UlisesGascon committed Dec 11, 2023
1 parent a31f9bf commit 186e6e0
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 122 deletions.
130 changes: 9 additions & 121 deletions src/node_file.cc
Expand Up @@ -29,6 +29,7 @@
#include "node_metadata.h"
#include "node_process-inl.h"
#include "node_stat_watcher.h"
#include "node_url.h"
#include "permission/permission.h"
#include "util-inl.h"

Expand Down Expand Up @@ -2847,123 +2848,6 @@ static void GetFormatOfExtensionlessFile(
return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT);
}

static bool FileURLToPath(
Environment* env,
const ada::url_aggregator& file_url,
/* The linter can't detect the assign for result_file_path
So we need to ignore since it suggest to put const */
// NOLINTNEXTLINE(runtime/references)
std::string& result_file_path) {
if (file_url.type != ada::scheme::FILE) {
env->isolate()->ThrowException(ERR_INVALID_URL_SCHEME(env->isolate()));

return false;
}

std::string_view pathname = file_url.get_pathname();
#ifdef _WIN32
size_t first_percent = std::string::npos;
size_t pathname_size = pathname.size();
std::string pathname_escaped_slash;

for (size_t i = 0; i < pathname_size; i++) {
if (pathname[i] == '/') {
pathname_escaped_slash += '\\';
} else {
pathname_escaped_slash += pathname[i];
}

if (pathname[i] != '%') continue;

if (first_percent == std::string::npos) {
first_percent = i;
}

// just safe-guard against access the pathname
// outside the bounds
if ((i + 2) >= pathname_size) continue;

char third = pathname[i + 2] | 0x20;

bool is_slash = pathname[i + 1] == '2' && third == 102;
bool is_forward_slash = pathname[i + 1] == '5' && third == 99;

if (!is_slash && !is_forward_slash) continue;

env->isolate()->ThrowException(ERR_INVALID_FILE_URL_PATH(
env->isolate(),
"File URL path must not include encoded \\ or / characters"));

return false;
}

std::string_view hostname = file_url.get_hostname();
std::string decoded_pathname = ada::unicode::percent_decode(
std::string_view(pathname_escaped_slash), first_percent);

if (hostname.size() > 0) {
// If hostname is set, then we have a UNC path
// Pass the hostname through domainToUnicode just in case
// it is an IDN using punycode encoding. We do not need to worry
// about percent encoding because the URL parser will have
// already taken care of that for us. Note that this only
// causes IDNs with an appropriate `xn--` prefix to be decoded.
result_file_path =
"\\\\" + ada::unicode::to_unicode(hostname) + decoded_pathname;

return true;
}

char letter = decoded_pathname[1] | 0x20;
char sep = decoded_pathname[2];

// a..z A..Z
if (letter < 'a' || letter > 'z' || sep != ':') {
env->isolate()->ThrowException(ERR_INVALID_FILE_URL_PATH(
env->isolate(), "File URL path must be absolute"));

return false;
}

result_file_path = decoded_pathname.substr(1);

return true;
#else // _WIN32
std::string_view hostname = file_url.get_hostname();

if (hostname.size() > 0) {
std::string error_message =
std::string("File URL host must be \"localhost\" or empty on ") +
std::string(per_process::metadata.platform);
env->isolate()->ThrowException(
ERR_INVALID_FILE_URL_HOST(env->isolate(), error_message.c_str()));

return false;
}

size_t first_percent = std::string::npos;
for (size_t i = 0; (i + 2) < pathname.size(); i++) {
if (pathname[i] != '%') continue;

if (first_percent == std::string::npos) {
first_percent = i;
}

if (pathname[i + 1] == '2' && (pathname[i + 2] | 0x20) == 102) {
env->isolate()->ThrowException(ERR_INVALID_FILE_URL_PATH(
env->isolate(),
"File URL path must not include encoded / characters"));

return false;
}
}

result_file_path = ada::unicode::percent_decode(pathname, first_percent);

return true;
#endif // _WIN32
}

BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile(
Environment* env, const std::string& file_path) {
THROW_IF_INSUFFICIENT_PERMISSIONS(
Expand Down Expand Up @@ -3051,7 +2935,9 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
return;
}

if (!FileURLToPath(env, file_path_url.value(), initial_file_path)) return;
if (!node::url::FileURLToPath(
env, file_path_url.value(), initial_file_path))
return;

FromNamespacedPath(&initial_file_path);

Expand Down Expand Up @@ -3085,7 +2971,8 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
return;
}

if (!FileURLToPath(env, file_path_url.value(), initial_file_path)) return;
if (!node::url::FileURLToPath(env, file_path_url.value(), initial_file_path))
return;

FromNamespacedPath(&initial_file_path);

Expand All @@ -3112,7 +2999,8 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
std::string module_path;
std::string module_base;

if (!FileURLToPath(env, package_json_url.value(), module_path)) return;
if (!node::url::FileURLToPath(env, package_json_url.value(), module_path))
return;

if (args.Length() >= 3 && !args[2]->IsNullOrUndefined() &&
args[2]->IsString()) {
Expand All @@ -3127,7 +3015,7 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
return;
}

if (!FileURLToPath(env, base_url.value(), module_base)) return;
if (!node::url::FileURLToPath(env, base_url.value(), module_base)) return;
} else {
std::string err_arg_message =
"The \"base\" argument must be of type string or an instance of URL.";
Expand Down
118 changes: 118 additions & 0 deletions src/node_url.cc
Expand Up @@ -4,6 +4,8 @@
#include "node_errors.h"
#include "node_external_reference.h"
#include "node_i18n.h"
#include "node_metadata.h"
#include "node_process-inl.h"
#include "util-inl.h"
#include "v8-fast-api-calls.h"
#include "v8.h"
Expand Down Expand Up @@ -435,6 +437,122 @@ std::string FromFilePath(std::string_view file_path) {
return ada::href_from_file(escaped_file_path);
}

bool FileURLToPath(Environment* env,
const ada::url_aggregator& file_url,
/* The linter can't detect the assign for result_file_path
So we need to ignore since it suggest to put const */
// NOLINTNEXTLINE(runtime/references)
std::string& result_file_path) {
if (file_url.type != ada::scheme::FILE) {
env->isolate()->ThrowException(ERR_INVALID_URL_SCHEME(env->isolate()));

return false;
}

std::string_view pathname = file_url.get_pathname();
#ifdef _WIN32
size_t first_percent = std::string::npos;
size_t pathname_size = pathname.size();
std::string pathname_escaped_slash;

for (size_t i = 0; i < pathname_size; i++) {
if (pathname[i] == '/') {
pathname_escaped_slash += '\\';
} else {
pathname_escaped_slash += pathname[i];
}

if (pathname[i] != '%') continue;

if (first_percent == std::string::npos) {
first_percent = i;
}

// just safe-guard against access the pathname
// outside the bounds
if ((i + 2) >= pathname_size) continue;

char third = pathname[i + 2] | 0x20;

bool is_slash = pathname[i + 1] == '2' && third == 102;
bool is_forward_slash = pathname[i + 1] == '5' && third == 99;

if (!is_slash && !is_forward_slash) continue;

env->isolate()->ThrowException(ERR_INVALID_FILE_URL_PATH(
env->isolate(),
"File URL path must not include encoded \\ or / characters"));

return false;
}

std::string_view hostname = file_url.get_hostname();
std::string decoded_pathname = ada::unicode::percent_decode(
std::string_view(pathname_escaped_slash), first_percent);

if (hostname.size() > 0) {
// If hostname is set, then we have a UNC path
// Pass the hostname through domainToUnicode just in case
// it is an IDN using punycode encoding. We do not need to worry
// about percent encoding because the URL parser will have
// already taken care of that for us. Note that this only
// causes IDNs with an appropriate `xn--` prefix to be decoded.
result_file_path =
"\\\\" + ada::unicode::to_unicode(hostname) + decoded_pathname;

return true;
}

char letter = decoded_pathname[1] | 0x20;
char sep = decoded_pathname[2];

// a..z A..Z
if (letter < 'a' || letter > 'z' || sep != ':') {
env->isolate()->ThrowException(ERR_INVALID_FILE_URL_PATH(
env->isolate(), "File URL path must be absolute"));

return false;
}

result_file_path = decoded_pathname.substr(1);

return true;
#else // _WIN32
std::string_view hostname = file_url.get_hostname();

if (hostname.size() > 0) {
std::string error_message =
std::string("File URL host must be \"localhost\" or empty on ") +
std::string(per_process::metadata.platform);
env->isolate()->ThrowException(
ERR_INVALID_FILE_URL_HOST(env->isolate(), error_message.c_str()));

return false;
}

size_t first_percent = std::string::npos;
for (size_t i = 0; (i + 2) < pathname.size(); i++) {
if (pathname[i] != '%') continue;

if (first_percent == std::string::npos) {
first_percent = i;
}

if (pathname[i + 1] == '2' && (pathname[i + 2] | 0x20) == 102) {
env->isolate()->ThrowException(ERR_INVALID_FILE_URL_PATH(
env->isolate(),
"File URL path must not include encoded / characters"));

return false;
}
}

result_file_path = ada::unicode::percent_decode(pathname, first_percent);

return true;
#endif // _WIN32
}

} // namespace url

} // namespace node
Expand Down
7 changes: 6 additions & 1 deletion src/node_url.h
Expand Up @@ -82,7 +82,12 @@ class BindingData : public SnapshotableObject {
};

std::string FromFilePath(std::string_view file_path);

bool FileURLToPath(Environment* env,
const ada::url_aggregator& file_url,
/* The linter can't detect the assign for result_file_path
So we need to ignore since it suggest to put const */
// NOLINTNEXTLINE(runtime/references)
std::string& result_file_path);
} // namespace url

} // namespace node
Expand Down

0 comments on commit 186e6e0

Please sign in to comment.