Skip to content

Commit f15ab36

Browse files
committed
url: refactor FileURLToPath
1 parent fce8fba commit f15ab36

File tree

3 files changed

+136
-141
lines changed

3 files changed

+136
-141
lines changed

src/node_file.cc

+32-141
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
#include "node_metadata.h"
3030
#include "node_process-inl.h"
3131
#include "node_stat_watcher.h"
32+
#include "node_url.h"
3233
#include "permission/permission.h"
3334
#include "util-inl.h"
3435

@@ -2842,123 +2843,6 @@ static void GetFormatOfExtensionlessFile(
28422843
return args.GetReturnValue().Set(EXTENSIONLESS_FORMAT_JAVASCRIPT);
28432844
}
28442845

2845-
static bool FileURLToPath(
2846-
Environment* env,
2847-
const ada::url_aggregator& file_url,
2848-
/* The linter can't detect the assign for result_file_path
2849-
So we need to ignore since it suggest to put const */
2850-
// NOLINTNEXTLINE(runtime/references)
2851-
std::string& result_file_path) {
2852-
if (file_url.type != ada::scheme::FILE) {
2853-
env->isolate()->ThrowException(ERR_INVALID_URL_SCHEME(env->isolate()));
2854-
2855-
return false;
2856-
}
2857-
2858-
std::string_view pathname = file_url.get_pathname();
2859-
#ifdef _WIN32
2860-
size_t first_percent = std::string::npos;
2861-
size_t pathname_size = pathname.size();
2862-
std::string pathname_escaped_slash;
2863-
2864-
for (size_t i = 0; i < pathname_size; i++) {
2865-
if (pathname[i] == '/') {
2866-
pathname_escaped_slash += '\\';
2867-
} else {
2868-
pathname_escaped_slash += pathname[i];
2869-
}
2870-
2871-
if (pathname[i] != '%') continue;
2872-
2873-
if (first_percent == std::string::npos) {
2874-
first_percent = i;
2875-
}
2876-
2877-
// just safe-guard against access the pathname
2878-
// outside the bounds
2879-
if ((i + 2) >= pathname_size) continue;
2880-
2881-
char third = pathname[i + 2] | 0x20;
2882-
2883-
bool is_slash = pathname[i + 1] == '2' && third == 102;
2884-
bool is_forward_slash = pathname[i + 1] == '5' && third == 99;
2885-
2886-
if (!is_slash && !is_forward_slash) continue;
2887-
2888-
env->isolate()->ThrowException(ERR_INVALID_FILE_URL_PATH(
2889-
env->isolate(),
2890-
"File URL path must not include encoded \\ or / characters"));
2891-
2892-
return false;
2893-
}
2894-
2895-
std::string_view hostname = file_url.get_hostname();
2896-
std::string decoded_pathname = ada::unicode::percent_decode(
2897-
std::string_view(pathname_escaped_slash), first_percent);
2898-
2899-
if (hostname.size() > 0) {
2900-
// If hostname is set, then we have a UNC path
2901-
// Pass the hostname through domainToUnicode just in case
2902-
// it is an IDN using punycode encoding. We do not need to worry
2903-
// about percent encoding because the URL parser will have
2904-
// already taken care of that for us. Note that this only
2905-
// causes IDNs with an appropriate `xn--` prefix to be decoded.
2906-
result_file_path =
2907-
"\\\\" + ada::unicode::to_unicode(hostname) + decoded_pathname;
2908-
2909-
return true;
2910-
}
2911-
2912-
char letter = decoded_pathname[1] | 0x20;
2913-
char sep = decoded_pathname[2];
2914-
2915-
// a..z A..Z
2916-
if (letter < 'a' || letter > 'z' || sep != ':') {
2917-
env->isolate()->ThrowException(ERR_INVALID_FILE_URL_PATH(
2918-
env->isolate(), "File URL path must be absolute"));
2919-
2920-
return false;
2921-
}
2922-
2923-
result_file_path = decoded_pathname.substr(1);
2924-
2925-
return true;
2926-
#else // _WIN32
2927-
std::string_view hostname = file_url.get_hostname();
2928-
2929-
if (hostname.size() > 0) {
2930-
std::string error_message =
2931-
std::string("File URL host must be \"localhost\" or empty on ") +
2932-
std::string(per_process::metadata.platform);
2933-
env->isolate()->ThrowException(
2934-
ERR_INVALID_FILE_URL_HOST(env->isolate(), error_message.c_str()));
2935-
2936-
return false;
2937-
}
2938-
2939-
size_t first_percent = std::string::npos;
2940-
for (size_t i = 0; (i + 2) < pathname.size(); i++) {
2941-
if (pathname[i] != '%') continue;
2942-
2943-
if (first_percent == std::string::npos) {
2944-
first_percent = i;
2945-
}
2946-
2947-
if (pathname[i + 1] == '2' && (pathname[i + 2] | 0x20) == 102) {
2948-
env->isolate()->ThrowException(ERR_INVALID_FILE_URL_PATH(
2949-
env->isolate(),
2950-
"File URL path must not include encoded / characters"));
2951-
2952-
return false;
2953-
}
2954-
}
2955-
2956-
result_file_path = ada::unicode::percent_decode(pathname, first_percent);
2957-
2958-
return true;
2959-
#endif // _WIN32
2960-
}
2961-
29622846
BindingData::FilePathIsFileReturnType BindingData::FilePathIsFile(
29632847
Environment* env, const std::string& file_path) {
29642848
THROW_IF_INSUFFICIENT_PERMISSIONS(
@@ -3021,14 +2905,11 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
30212905
ada::parse<ada::url_aggregator>(utf8_package_json_url.ToStringView());
30222906

30232907
if (!package_json_url) {
3024-
env->isolate()->ThrowException(
3025-
ERR_INVALID_URL(env->isolate(), "Invalid URL"));
3026-
2908+
THROW_ERR_INVALID_URL(env->isolate(), "Invalid URL");
30272909
return;
30282910
}
30292911

30302912
ada::result<ada::url_aggregator> file_path_url;
3031-
std::string initial_file_path;
30322913
std::string file_path;
30332914

30342915
if (args.Length() >= 2 && !args[1]->IsNullOrUndefined() &&
@@ -3040,18 +2921,20 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
30402921
std::string("./") + package_config_main, &package_json_url.value());
30412922

30422923
if (!file_path_url) {
3043-
env->isolate()->ThrowException(
3044-
ERR_INVALID_URL(env->isolate(), "Invalid URL"));
3045-
2924+
THROW_ERR_INVALID_URL(env->isolate(), "Invalid URL");
30462925
return;
30472926
}
30482927

3049-
if (!FileURLToPath(env, file_path_url.value(), initial_file_path)) return;
2928+
auto file_url = node::url::FileURLToPath(env, *file_path_url);
30502929

3051-
FromNamespacedPath(&initial_file_path);
2930+
if (!file_url.has_value()) {
2931+
return;
2932+
}
2933+
2934+
FromNamespacedPath(&file_url.value());
30522935

30532936
for (int i = 0; i < legacy_main_extensions_with_main_end; i++) {
3054-
file_path = initial_file_path + std::string(legacy_main_extensions[i]);
2937+
file_path = *file_url + std::string(legacy_main_extensions[i]);
30552938

30562939
switch (FilePathIsFile(env, file_path)) {
30572940
case BindingData::FilePathIsFileReturnType::kIsFile:
@@ -3080,14 +2963,18 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
30802963
return;
30812964
}
30822965

3083-
if (!FileURLToPath(env, file_path_url.value(), initial_file_path)) return;
2966+
auto file_url = node::url::FileURLToPath(env, *file_path_url);
2967+
2968+
if (!file_url.has_value()) {
2969+
return;
2970+
}
30842971

3085-
FromNamespacedPath(&initial_file_path);
2972+
FromNamespacedPath(&file_url.value());
30862973

30872974
for (int i = legacy_main_extensions_with_main_end;
30882975
i < legacy_main_extensions_package_fallback_end;
30892976
i++) {
3090-
file_path = initial_file_path + std::string(legacy_main_extensions[i]);
2977+
file_path = *file_url + std::string(legacy_main_extensions[i]);
30912978

30922979
switch (FilePathIsFile(env, file_path)) {
30932980
case BindingData::FilePathIsFileReturnType::kIsFile:
@@ -3104,25 +2991,29 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
31042991
}
31052992
}
31062993

3107-
std::string module_path;
3108-
std::string module_base;
2994+
std::optional<std::string> module_path =
2995+
node::url::FileURLToPath(env, *package_json_url);
2996+
std::optional<std::string> module_base;
31092997

3110-
if (!FileURLToPath(env, package_json_url.value(), module_path)) return;
2998+
if (!module_path.has_value()) {
2999+
return;
3000+
}
31113001

3112-
if (args.Length() >= 3 && !args[2]->IsNullOrUndefined() &&
3113-
args[2]->IsString()) {
3002+
if (args.Length() >= 3 && args[2]->IsString()) {
31143003
Utf8Value utf8_base_path(env->isolate(), args[2].As<String>());
31153004
auto base_url =
31163005
ada::parse<ada::url_aggregator>(utf8_base_path.ToStringView());
31173006

31183007
if (!base_url) {
3119-
env->isolate()->ThrowException(
3120-
ERR_INVALID_URL(env->isolate(), "Invalid URL"));
3121-
3008+
THROW_ERR_INVALID_URL(env->isolate(), "Invalid URL");
31223009
return;
31233010
}
31243011

3125-
if (!FileURLToPath(env, base_url.value(), module_base)) return;
3012+
module_base = node::url::FileURLToPath(env, *base_url);
3013+
3014+
if (!module_base.has_value()) {
3015+
return;
3016+
}
31263017
} else {
31273018
std::string err_arg_message =
31283019
"The \"base\" argument must be of type string or an instance of URL.";
@@ -3134,8 +3025,8 @@ void BindingData::LegacyMainResolve(const FunctionCallbackInfo<Value>& args) {
31343025
env->isolate()->ThrowException(
31353026
ERR_MODULE_NOT_FOUND(env->isolate(),
31363027
"Cannot find package '%s' imported from %s",
3137-
module_path,
3138-
module_base));
3028+
*module_path,
3029+
*module_base));
31393030
}
31403031

31413032
void BindingData::MemoryInfo(MemoryTracker* tracker) const {

src/node_url.cc

+102
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#include "node_errors.h"
55
#include "node_external_reference.h"
66
#include "node_i18n.h"
7+
#include "node_metadata.h"
8+
#include "node_process-inl.h"
79
#include "util-inl.h"
810
#include "v8-fast-api-calls.h"
911
#include "v8.h"
@@ -426,6 +428,106 @@ std::string FromFilePath(const std::string_view file_path) {
426428
return ada::href_from_file(escaped_file_path);
427429
}
428430

431+
std::optional<std::string> FileURLToPath(Environment* env,
432+
const ada::url_aggregator& file_url) {
433+
if (file_url.type != ada::scheme::FILE) {
434+
THROW_ERR_INVALID_URL_SCHEME(env->isolate());
435+
return std::nullopt;
436+
}
437+
438+
std::string_view pathname = file_url.get_pathname();
439+
#ifdef _WIN32
440+
size_t first_percent = std::string::npos;
441+
size_t pathname_size = pathname.size();
442+
std::string pathname_escaped_slash;
443+
444+
for (size_t i = 0; i < pathname_size; i++) {
445+
if (pathname[i] == '/') {
446+
pathname_escaped_slash += '\\';
447+
} else {
448+
pathname_escaped_slash += pathname[i];
449+
}
450+
451+
if (pathname[i] != '%') continue;
452+
453+
if (first_percent == std::string::npos) {
454+
first_percent = i;
455+
}
456+
457+
// just safe-guard against access the pathname
458+
// outside the bounds
459+
if ((i + 2) >= pathname_size) continue;
460+
461+
char third = pathname[i + 2] | 0x20;
462+
463+
bool is_slash = pathname[i + 1] == '2' && third == 102;
464+
bool is_forward_slash = pathname[i + 1] == '5' && third == 99;
465+
466+
if (!is_slash && !is_forward_slash) continue;
467+
468+
THROW_ERR_INVALID_FILE_URL_PATH(
469+
env->isolate(),
470+
"File URL path must not include encoded \\ or / characters");
471+
472+
return std::nullopt;
473+
}
474+
475+
std::string_view hostname = file_url.get_hostname();
476+
std::string decoded_pathname =
477+
ada::unicode::percent_decode(pathname_escaped_slash, first_percent);
478+
479+
if (hostname.size() > 0) {
480+
// If hostname is set, then we have a UNC path
481+
// Pass the hostname through domainToUnicode just in case
482+
// it is an IDN using punycode encoding. We do not need to worry
483+
// about percent encoding because the URL parser will have
484+
// already taken care of that for us. Note that this only
485+
// causes IDNs with an appropriate `xn--` prefix to be decoded.
486+
return "\\\\" + ada::unicode::to_unicode(hostname) + decoded_pathname;
487+
}
488+
489+
char letter = decoded_pathname[1] | 0x20;
490+
char sep = decoded_pathname[2];
491+
492+
// a..z A..Z
493+
if (letter < 'a' || letter > 'z' || sep != ':') {
494+
THROW_ERR_NIVALID_FILE_URL_PATH(env->isolate(),
495+
"File URL path must be absolute");
496+
return std::nullopt;
497+
}
498+
499+
return decoded_pathname.substr(1);
500+
#else // _WIN32
501+
std::string_view hostname = file_url.get_hostname();
502+
503+
if (hostname.size() > 0) {
504+
THROW_ERR_INVALID_FILE_URL_HOST(
505+
env->isolate(),
506+
"File URL host must be \"localhost\" or empty on %s",
507+
std::string(per_process::metadata.platform));
508+
return std::nullopt;
509+
}
510+
511+
size_t first_percent = std::string::npos;
512+
for (size_t i = 0; (i + 2) < pathname.size(); i++) {
513+
if (pathname[i] != '%') continue;
514+
515+
if (first_percent == std::string::npos) {
516+
first_percent = i;
517+
}
518+
519+
if (pathname[i + 1] == '2' && (pathname[i + 2] | 0x20) == 102) {
520+
THROW_ERR_INVALID_FILE_URL_PATH(
521+
env->isolate(),
522+
"File URL path must not include encoded / characters");
523+
return std::nullopt;
524+
}
525+
}
526+
527+
return ada::unicode::percent_decode(pathname, first_percent);
528+
#endif // _WIN32
529+
}
530+
429531
} // namespace url
430532

431533
} // namespace node

src/node_url.h

+2
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ class BindingData : public SnapshotableObject {
8282
};
8383

8484
std::string FromFilePath(const std::string_view file_path);
85+
std::optional<std::string> FileURLToPath(Environment* env,
86+
const ada::url_aggregator& file_url);
8587

8688
} // namespace url
8789

0 commit comments

Comments
 (0)