-
Notifications
You must be signed in to change notification settings - Fork 30k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
lib,src,permission: port path.resolve to C++
Co-Authored-By: Carlos Espa <[email protected]> PR-URL: #50758 Refs: nodejs/security-wg#898 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Paolo Insogna <[email protected]> Reviewed-By: Claudio Wunder <[email protected]>
- Loading branch information
1 parent
b616f6f
commit 3badc11
Showing
18 changed files
with
416 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
#include "path.h" | ||
#include <string> | ||
#include <vector> | ||
#include "env-inl.h" | ||
#include "node_internals.h" | ||
#include "util.h" | ||
|
||
namespace node { | ||
|
||
#ifdef _WIN32 | ||
bool IsPathSeparator(const char c) noexcept { | ||
return c == kPathSeparator || c == '/'; | ||
} | ||
#else // POSIX | ||
bool IsPathSeparator(const char c) noexcept { | ||
return c == kPathSeparator; | ||
} | ||
#endif // _WIN32 | ||
|
||
std::string NormalizeString(const std::string_view path, | ||
bool allowAboveRoot, | ||
const std::string_view separator) { | ||
std::string res; | ||
int lastSegmentLength = 0; | ||
int lastSlash = -1; | ||
int dots = 0; | ||
char code; | ||
const auto pathLen = path.size(); | ||
for (uint8_t i = 0; i <= pathLen; ++i) { | ||
if (i < pathLen) { | ||
code = path[i]; | ||
} else if (IsPathSeparator(path[i])) { | ||
break; | ||
} else { | ||
code = node::kPathSeparator; | ||
} | ||
|
||
if (IsPathSeparator(code)) { | ||
if (lastSlash == static_cast<int>(i - 1) || dots == 1) { | ||
// NOOP | ||
} else if (dots == 2) { | ||
int len = res.length(); | ||
if (len < 2 || lastSegmentLength != 2 || res[len - 1] != '.' || | ||
res[len - 2] != '.') { | ||
if (len > 2) { | ||
auto lastSlashIndex = res.find_last_of(separator); | ||
if (lastSlashIndex == std::string::npos) { | ||
res = ""; | ||
lastSegmentLength = 0; | ||
} else { | ||
res = res.substr(0, lastSlashIndex); | ||
len = res.length(); | ||
lastSegmentLength = len - 1 - res.find_last_of(separator); | ||
} | ||
lastSlash = i; | ||
dots = 0; | ||
continue; | ||
} else if (len != 0) { | ||
res = ""; | ||
lastSegmentLength = 0; | ||
lastSlash = i; | ||
dots = 0; | ||
continue; | ||
} | ||
} | ||
|
||
if (allowAboveRoot) { | ||
res += res.length() > 0 ? std::string(separator) + ".." : ".."; | ||
lastSegmentLength = 2; | ||
} | ||
} else { | ||
if (!res.empty()) { | ||
res += std::string(separator) + | ||
std::string(path.substr(lastSlash + 1, i - (lastSlash + 1))); | ||
} else { | ||
res = path.substr(lastSlash + 1, i - (lastSlash + 1)); | ||
} | ||
lastSegmentLength = i - lastSlash - 1; | ||
} | ||
lastSlash = i; | ||
dots = 0; | ||
} else if (code == '.' && dots != -1) { | ||
++dots; | ||
} else { | ||
dots = -1; | ||
} | ||
} | ||
|
||
return res; | ||
} | ||
|
||
#ifdef _WIN32 | ||
bool IsWindowsDeviceRoot(const char c) noexcept { | ||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); | ||
} | ||
|
||
std::string PathResolve(Environment* env, | ||
const std::vector<std::string_view>& paths) { | ||
std::string resolvedDevice = ""; | ||
std::string resolvedTail = ""; | ||
bool resolvedAbsolute = false; | ||
const size_t numArgs = paths.size(); | ||
auto cwd = env->GetCwd(env->exec_path()); | ||
|
||
for (int i = numArgs - 1; i >= -1 && !resolvedAbsolute; i--) { | ||
std::string path; | ||
if (i >= 0) { | ||
path = std::string(paths[i]); | ||
} else if (resolvedDevice.empty()) { | ||
path = cwd; | ||
} else { | ||
// Windows has the concept of drive-specific current working | ||
// directories. If we've resolved a drive letter but not yet an | ||
// absolute path, get cwd for that drive, or the process cwd if | ||
// the drive cwd is not available. We're sure the device is not | ||
// a UNC path at this points, because UNC paths are always absolute. | ||
std::string resolvedDevicePath; | ||
const std::string envvar = "=" + resolvedDevice; | ||
credentials::SafeGetenv(envvar.c_str(), &resolvedDevicePath); | ||
path = resolvedDevicePath.empty() ? cwd : resolvedDevicePath; | ||
|
||
// Verify that a cwd was found and that it actually points | ||
// to our drive. If not, default to the drive's root. | ||
if (path.empty() || | ||
(ToLower(path.substr(0, 2)) != ToLower(resolvedDevice) && | ||
path[2] == '/')) { | ||
path = resolvedDevice + "\\"; | ||
} | ||
} | ||
|
||
const size_t len = path.length(); | ||
int rootEnd = 0; | ||
std::string device = ""; | ||
bool isAbsolute = false; | ||
const char code = path[0]; | ||
|
||
// Try to match a root | ||
if (len == 1) { | ||
if (IsPathSeparator(code)) { | ||
// `path` contains just a path separator | ||
rootEnd = 1; | ||
isAbsolute = true; | ||
} | ||
} else if (IsPathSeparator(code)) { | ||
// Possible UNC root | ||
|
||
// If we started with a separator, we know we at least have an | ||
// absolute path of some kind (UNC or otherwise) | ||
isAbsolute = true; | ||
|
||
if (IsPathSeparator(path[1])) { | ||
// Matched double path separator at beginning | ||
size_t j = 2; | ||
size_t last = j; | ||
// Match 1 or more non-path separators | ||
while (j < len && !IsPathSeparator(path[j])) { | ||
j++; | ||
} | ||
if (j < len && j != last) { | ||
const std::string firstPart = path.substr(last, j - last); | ||
// Matched! | ||
last = j; | ||
// Match 1 or more path separators | ||
while (j < len && IsPathSeparator(path[j])) { | ||
j++; | ||
} | ||
if (j < len && j != last) { | ||
// Matched! | ||
last = j; | ||
// Match 1 or more non-path separators | ||
while (j < len && !IsPathSeparator(path[j])) { | ||
j++; | ||
} | ||
if (j == len || j != last) { | ||
// We matched a UNC root | ||
device = "\\\\" + firstPart + "\\" + path.substr(last, j - last); | ||
rootEnd = j; | ||
} | ||
} | ||
} | ||
} | ||
} else if (IsWindowsDeviceRoot(code) && path[1] == ':') { | ||
// Possible device root | ||
device = path.substr(0, 2); | ||
rootEnd = 2; | ||
if (len > 2 && IsPathSeparator(path[2])) { | ||
// Treat separator following drive name as an absolute path | ||
// indicator | ||
isAbsolute = true; | ||
rootEnd = 3; | ||
} | ||
} | ||
|
||
if (!device.empty()) { | ||
if (!resolvedDevice.empty()) { | ||
if (ToLower(device) != ToLower(resolvedDevice)) { | ||
// This path points to another device so it is not applicable | ||
continue; | ||
} | ||
} else { | ||
resolvedDevice = device; | ||
} | ||
} | ||
|
||
if (resolvedAbsolute) { | ||
if (!resolvedDevice.empty()) { | ||
break; | ||
} | ||
} else { | ||
resolvedTail = path.substr(rootEnd) + "\\" + resolvedTail; | ||
resolvedAbsolute = isAbsolute; | ||
if (isAbsolute && !resolvedDevice.empty()) { | ||
break; | ||
} | ||
} | ||
} | ||
|
||
// At this point the path should be resolved to a full absolute path, | ||
// but handle relative paths to be safe (might happen when process.cwd() | ||
// fails) | ||
|
||
// Normalize the tail path | ||
resolvedTail = NormalizeString(resolvedTail, !resolvedAbsolute, "\\"); | ||
|
||
if (resolvedAbsolute) { | ||
return resolvedDevice + "\\" + resolvedTail; | ||
} | ||
|
||
if (!resolvedDevice.empty() || !resolvedTail.empty()) { | ||
return resolvedDevice + resolvedTail; | ||
} | ||
|
||
return "."; | ||
} | ||
#else // _WIN32 | ||
std::string PathResolve(Environment* env, | ||
const std::vector<std::string_view>& paths) { | ||
std::string resolvedPath; | ||
bool resolvedAbsolute = false; | ||
auto cwd = env->GetCwd(env->exec_path()); | ||
const size_t numArgs = paths.size(); | ||
|
||
for (int i = numArgs - 1; i >= -1 && !resolvedAbsolute; i--) { | ||
const std::string& path = | ||
(i >= 0) ? std::string(paths[i]) : env->GetCwd(env->exec_path()); | ||
|
||
if (!path.empty()) { | ||
resolvedPath = std::string(path) + "/" + resolvedPath; | ||
|
||
if (path.front() == '/') { | ||
resolvedAbsolute = true; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
// Normalize the path | ||
auto normalizedPath = NormalizeString(resolvedPath, !resolvedAbsolute, "/"); | ||
|
||
if (resolvedAbsolute) { | ||
return "/" + normalizedPath; | ||
} | ||
|
||
if (normalizedPath.empty()) { | ||
return "."; | ||
} | ||
|
||
return normalizedPath; | ||
} | ||
#endif // _WIN32 | ||
|
||
} // namespace node |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
#ifndef SRC_PATH_H_ | ||
#define SRC_PATH_H_ | ||
|
||
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | ||
|
||
#include <string> | ||
#include <vector> | ||
|
||
namespace node { | ||
|
||
class Environment; | ||
|
||
bool IsPathSeparator(const char c) noexcept; | ||
|
||
std::string NormalizeString(const std::string_view path, | ||
bool allowAboveRoot, | ||
const std::string_view separator); | ||
|
||
std::string PathResolve(Environment* env, | ||
const std::vector<std::string_view>& args); | ||
} // namespace node | ||
|
||
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS | ||
|
||
#endif // SRC_PATH_H_ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.