2121#include < string_view>
2222#include < utility>
2323
24+ #include " absl/strings/ascii.h" // IWYU pragma: keep
25+ #include " absl/strings/match.h" // IWYU pragma: keep
2426#include " absl/strings/str_cat.h"
2527#include " absl/strings/string_view.h"
28+ #include " tensorstore/internal/ascii_set.h" // IWYU pragma: keep
2629
30+ namespace tensorstore {
2731namespace {
2832
2933#ifdef _WIN32
3034constexpr inline bool IsDirSeparator (char c) { return c == ' \\ ' || c == ' /' ; }
35+
36+ bool IsWindowsDriveLetter (std::string_view path) {
37+ return path.length () >= 2 && path[1 ] == ' :' && absl::ascii_isalpha (path[0 ]);
38+ }
39+
40+ static constexpr internal::AsciiSet kIllegalUNCCharacters {" <>:/\\\" |'?*" };
41+
3142#else
3243constexpr inline bool IsDirSeparator (char c) { return c == ' /' ; }
3344#endif
3445
3546} // namespace
36- namespace tensorstore {
3747namespace internal_path {
3848
3949std::string JoinPathImpl (std::initializer_list<std::string_view> paths) {
@@ -56,20 +66,22 @@ namespace internal {
5666// Splits a path into the pair {dirname, basename}
5767std::pair<std::string_view, std::string_view> PathDirnameBasename (
5868 std::string_view path) {
69+ // Find the root directory.
70+ auto root_dir = PathRootName (path);
71+ if (root_dir.size () < path.size () && IsDirSeparator (path[root_dir.size ()])) {
72+ root_dir = path.substr (0 , root_dir.size () + 1 );
73+ }
74+
5975 size_t pos = path.size ();
60- while (pos != 0 && !IsDirSeparator (path[pos - 1 ])) {
76+ while (pos > root_dir. size () && !IsDirSeparator (path[pos - 1 ])) {
6177 --pos;
6278 }
6379 size_t basename = pos;
64- --pos;
65- if (pos == std::string_view::npos) {
66- return {" " , path};
67- }
68- while (pos != 0 && IsDirSeparator (path[pos - 1 ])) {
80+ if (pos > root_dir.size ()) {
6981 --pos;
70- }
71- if (pos == 0 ) {
72- return { " / " , path. substr (basename)};
82+ while (pos > root_dir. size () && IsDirSeparator (path[pos - 1 ])) {
83+ --pos;
84+ }
7385 }
7486 return {path.substr (0 , pos), path.substr (basename)};
7587}
@@ -99,20 +111,50 @@ void AppendPathComponent(std::string& path, std::string_view component) {
99111 }
100112}
101113
114+ std::string_view PathRootName (std::string_view path) {
115+ #ifdef _WIN32
116+ if (path.empty ()) return {};
117+ if (IsWindowsDriveLetter (path)) {
118+ return path.substr (0 , 2 );
119+ }
120+
121+ // Handle windows network shares, only on Windows
122+ if (absl::StartsWith (path, " \\\\ " ) || absl::StartsWith (path, " //" )) {
123+ size_t prefix = 2 ;
124+ while (prefix < path.size () && path[prefix] >= 31 &&
125+ !kIllegalUNCCharacters .Test (path[prefix])) {
126+ ++prefix;
127+ }
128+ if (prefix > 2 && prefix < path.length () &&
129+ (path[prefix] == ' /' || path[prefix] == ' \\ ' )) {
130+ return path.substr (0 , prefix);
131+ }
132+ }
133+ #endif
134+ return {};
135+ }
136+
102137std::string LexicalNormalizePath (std::string path) {
103138 if (path.empty ()) return path;
104139
105140 const char * src = path.c_str ();
106141 auto dst = path.begin ();
107142
108- // Remove initial '/' characters.
109- // Assume that Windows paths do not begin with '/'.
110- const bool is_absolute_path = (*src == ' /' );
143+ // Skip the root name if present.
144+ if (auto root_name = PathRootName (path); !root_name.empty ()) {
145+ dst += root_name.size ();
146+ src += root_name.size ();
147+ }
148+
149+ // A root directory begins after the root name; if it is present,
150+ // copy it and skip any duplicate separators.
151+ const bool is_absolute_path = IsDirSeparator (*src);
111152 if (is_absolute_path) {
112- dst++;
153+ * dst++ = ' / ' ;
113154 src++;
114- while (*src == ' / ' ) ++src;
155+ while (IsDirSeparator ( *src) ) ++src;
115156 }
157+
116158 auto limit = dst;
117159
118160 // Process all parts
@@ -175,5 +217,12 @@ std::string LexicalNormalizePath(std::string path) {
175217 return path;
176218}
177219
220+ bool IsAbsolutePath (std::string_view path) {
221+ if (path.empty ()) return false ;
222+ auto root_name = PathRootName (path);
223+ return path.size () > root_name.size () &&
224+ IsDirSeparator (path[root_name.size ()]);
225+ }
226+
178227} // namespace internal
179228} // namespace tensorstore
0 commit comments