diff --git a/src/node_file.cc b/src/node_file.cc index bda1774c012e7b..1f9147c456c0bc 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -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" @@ -2812,123 +2813,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( @@ -3016,7 +2900,9 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo& 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); @@ -3050,7 +2936,8 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo& 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); @@ -3077,7 +2964,8 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo& 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()) { @@ -3092,7 +2980,7 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo& 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."; diff --git a/src/node_url.cc b/src/node_url.cc index ccc148386e5a67..dec5d640fc2b8f 100644 --- a/src/node_url.cc +++ b/src/node_url.cc @@ -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" @@ -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 diff --git a/src/node_url.h b/src/node_url.h index ca59a375d1e2af..8da7722f1f6ef8 100644 --- a/src/node_url.h +++ b/src/node_url.h @@ -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