1 /**
2  * Expose GenericPath & other symbols
3  *
4  * Becase `vibe.core.path` defines `NativePath` & co and we redefine it,
5  * we need this extra module to avoid symbol conflicts.
6  */
7 module dub.internal.vibecompat.inet.path2;
8 
9 version (Have_vibe_core) public import vibe.core.path;
10 else:
11 
12 import std.algorithm.searching : commonPrefix, endsWith, startsWith;
13 import std.algorithm.comparison : equal, min;
14 import std.algorithm.iteration : map;
15 import std.exception : enforce;
16 import std.range : empty, front, popFront, popFrontExactly, takeExactly;
17 import std.range.primitives : ElementType, isInputRange, isOutputRange, isForwardRange, save;
18 import std.traits : isArray, isInstanceOf, isSomeChar;
19 import std.utf : byChar;
20 
21 // Original aliases
22 package(dub.internal.vibecompat.inet) alias WindowsPath = GenericPath!WindowsPathFormat;
23 package(dub.internal.vibecompat.inet) alias PosixPath = GenericPath!PosixPathFormat;
24 package(dub.internal.vibecompat.inet) alias InetPath = GenericPath!InetPathFormat;
25 version (Windows)
26     package(dub.internal.vibecompat.inet) alias NativePath = WindowsPath;
27 else
28      package(dub.internal.vibecompat.inet) alias NativePath = PosixPath;
29 
30 // for testing only, real one in path
31 private string toNativeString(T)(T path)
32 {
33     return (cast(NativePath)path).toString();
34 }
35 
36 /** Computes the relative path from `base_path` to this path.
37 
38 	Params:
39 		path = The destination path
40 		base_path = The path from which the relative path starts
41 
42 	See_also: `relativeToWeb`
43 */
44 Path relativeTo(Path)(in Path path, in Path base_path) @safe
45 	if (isInstanceOf!(GenericPath, Path))
46 {
47 	import std.array : array, replicate;
48 	import std.range : chain, drop, take;
49 
50 	assert(base_path.absolute, "Base path must be absolute for relativeTo.");
51 	assert(path.absolute, "Path must be absolute for relativeTo.");
52 
53 	if (is(Path.Format == WindowsPathFormat)) { // FIXME: this shouldn't be a special case here!
54 		bool samePrefix(size_t n)
55 		{
56 			return path.bySegment.map!(n => n.encodedName).take(n).equal(base_path.bySegment.map!(n => n.encodedName).take(n));
57 		}
58 		// a path such as ..\C:\windows is not valid, so force the path to stay absolute in this case
59 		auto pref = path.bySegment;
60 		if (!pref.empty && pref.front.encodedName == "") {
61 			pref.popFront();
62 			if (!pref.empty) {
63 				// different drive?
64 				if (pref.front.encodedName.endsWith(':') && !samePrefix(2))
65 					return path;
66 				// different UNC path?
67 				if (pref.front.encodedName == "" && !samePrefix(4))
68 					return path;
69 			}
70 		}
71 	}
72 
73 	auto nodes = path.bySegment;
74 	auto base_nodes = base_path.bySegment;
75 
76 	// skip and count common prefix
77 	size_t base = 0;
78 	while (!nodes.empty && !base_nodes.empty && equal(nodes.front.name, base_nodes.front.name)) {
79 		nodes.popFront();
80 		base_nodes.popFront();
81 		base++;
82 	}
83 
84 	enum up = Path.Segment2("..", Path.defaultSeparator);
85 	auto ret = Path(base_nodes.map!(p => up).chain(nodes));
86 	if (path.endsWithSlash) {
87 		if (ret.empty) return Path.fromTrustedString("." ~ path.toString()[$-1]);
88 		else ret.endsWithSlash = true;
89 	}
90 	return ret;
91 }
92 
93 ///
94 unittest {
95 	import std.array : array;
96 	import std.conv : to;
97 	assert(PosixPath("/some/path").relativeTo(PosixPath("/")) == PosixPath("some/path"));
98 	assert(PosixPath("/some/path/").relativeTo(PosixPath("/some/other/path/")) == PosixPath("../../path/"));
99 	assert(PosixPath("/some/path/").relativeTo(PosixPath("/some/other/path")) == PosixPath("../../path/"));
100 
101 	assert(WindowsPath("C:\\some\\path").relativeTo(WindowsPath("C:\\")) == WindowsPath("some\\path"));
102 	assert(WindowsPath("C:\\some\\path\\").relativeTo(WindowsPath("C:\\some\\other\\path/")) == WindowsPath("..\\..\\path\\"));
103 	assert(WindowsPath("C:\\some\\path\\").relativeTo(WindowsPath("C:\\some\\other\\path")) == WindowsPath("..\\..\\path\\"));
104 
105 	assert(WindowsPath("\\\\server\\share\\some\\path").relativeTo(WindowsPath("\\\\server\\share\\")) == WindowsPath("some\\path"));
106 	assert(WindowsPath("\\\\server\\share\\some\\path\\").relativeTo(WindowsPath("\\\\server\\share\\some\\other\\path/")) == WindowsPath("..\\..\\path\\"));
107 	assert(WindowsPath("\\\\server\\share\\some\\path\\").relativeTo(WindowsPath("\\\\server\\share\\some\\other\\path")) == WindowsPath("..\\..\\path\\"));
108 
109 	assert(WindowsPath("C:\\some\\path").relativeTo(WindowsPath("D:\\")) == WindowsPath("C:\\some\\path"));
110 	assert(WindowsPath("C:\\some\\path\\").relativeTo(WindowsPath("\\\\server\\share")) == WindowsPath("C:\\some\\path\\"));
111 	assert(WindowsPath("\\\\server\\some\\path\\").relativeTo(WindowsPath("C:\\some\\other\\path")) == WindowsPath("\\\\server\\some\\path\\"));
112 	assert(WindowsPath("\\\\server\\some\\path\\").relativeTo(WindowsPath("\\\\otherserver\\path")) == WindowsPath("\\\\server\\some\\path\\"));
113 	assert(WindowsPath("\\some\\path\\").relativeTo(WindowsPath("\\other\\path")) == WindowsPath("..\\..\\some\\path\\"));
114 
115 	assert(WindowsPath("\\\\server\\share\\path1").relativeTo(WindowsPath("\\\\server\\share\\path2")) == WindowsPath("..\\path1"));
116 	assert(WindowsPath("\\\\server\\share\\path1").relativeTo(WindowsPath("\\\\server\\share2\\path2")) == WindowsPath("\\\\server\\share\\path1"));
117 	assert(WindowsPath("\\\\server\\share\\path1").relativeTo(WindowsPath("\\\\server2\\share2\\path2")) == WindowsPath("\\\\server\\share\\path1"));
118 }
119 
120 unittest {
121 	{
122 		auto parentpath = "/path/to/parent";
123 		auto parentpathp = PosixPath(parentpath);
124 		auto subpath = "/path/to/parent/sub/";
125 		auto subpathp = PosixPath(subpath);
126 		auto subpath_rel = "sub/";
127 		assert(subpathp.relativeTo(parentpathp).toString() == subpath_rel);
128 		auto subfile = "/path/to/parent/child";
129 		auto subfilep = PosixPath(subfile);
130 		auto subfile_rel = "child";
131 		assert(subfilep.relativeTo(parentpathp).toString() == subfile_rel);
132 	}
133 
134 	{ // relative paths across Windows devices are not allowed
135 		auto p1 = WindowsPath("\\\\server\\share"); assert(p1.absolute);
136 		auto p2 = WindowsPath("\\\\server\\othershare"); assert(p2.absolute);
137 		auto p3 = WindowsPath("\\\\otherserver\\share"); assert(p3.absolute);
138 		auto p4 = WindowsPath("C:\\somepath"); assert(p4.absolute);
139 		auto p5 = WindowsPath("C:\\someotherpath"); assert(p5.absolute);
140 		auto p6 = WindowsPath("D:\\somepath"); assert(p6.absolute);
141 		auto p7 = WindowsPath("\\\\server\\share\\path"); assert(p7.absolute);
142 		auto p8 = WindowsPath("\\\\server\\share\\otherpath"); assert(p8.absolute);
143 		assert(p4.relativeTo(p5) == WindowsPath("..\\somepath"));
144 		assert(p4.relativeTo(p6) == WindowsPath("C:\\somepath"));
145 		assert(p4.relativeTo(p1) == WindowsPath("C:\\somepath"));
146 		assert(p1.relativeTo(p2) == WindowsPath("\\\\server\\share"));
147 		assert(p1.relativeTo(p3) == WindowsPath("\\\\server\\share"));
148 		assert(p1.relativeTo(p4) == WindowsPath("\\\\server\\share"));
149 		assert(p7.relativeTo(p1) == WindowsPath("path"));
150 		assert(p7.relativeTo(p8) == WindowsPath("..\\path"));
151 	}
152 
153 	{ // relative path, trailing slash
154 		auto p1 = PosixPath("/some/path");
155 		auto p2 = PosixPath("/some/path/");
156 		assert(p1.relativeTo(p1).toString() == "");
157 		assert(p1.relativeTo(p2).toString() == "");
158 		assert(p2.relativeTo(p2).toString() == "./");
159 	}
160 
161     {
162         immutable PosixPath p1 = PosixPath("/foo/bar");
163         immutable PosixPath p2 = PosixPath("/foo");
164         immutable PosixPath result = p1.relativeTo(p2);
165         assert(result == PosixPath("bar"));
166     }
167 }
168 
169 nothrow unittest {
170 	auto p1 = PosixPath.fromTrustedString("/foo/bar/baz");
171 	auto p2 = PosixPath.fromTrustedString("/foo/baz/bam");
172 	assert(p2.relativeTo(p1).toString == "../../baz/bam");
173 }
174 
175 
176 /** Computes the relative path to this path from `base_path` using web path rules.
177 
178 	The difference to `relativeTo` is that a path not ending in a slash
179 	will not be considered as a path to a directory and the parent path
180 	will instead be used.
181 
182 	Params:
183 		path = The destination path
184 		base_path = The path from which the relative path starts
185 
186 	See_also: `relativeTo`
187 */
188 Path relativeToWeb(Path)(Path path, Path base_path) @safe
189 	if (isInstanceOf!(GenericPath, Path))
190 {
191 	if (!base_path.endsWithSlash) {
192 		assert(base_path.absolute, "Base path must be absolute for relativeToWeb.");
193 		if (base_path.hasParentPath) base_path = base_path.parentPath;
194 		else base_path = Path("/");
195 		assert(base_path.absolute);
196 	}
197 	return path.relativeTo(base_path);
198 }
199 
200 ///
201 /+unittest {
202 	assert(InetPath("/some/path").relativeToWeb(InetPath("/")) == InetPath("some/path"));
203 	assert(InetPath("/some/path/").relativeToWeb(InetPath("/some/other/path/")) == InetPath("../../path/"));
204 	assert(InetPath("/some/path/").relativeToWeb(InetPath("/some/other/path")) == InetPath("../path/"));
205 }+/
206 
207 /// Provides a common interface to operate on paths of various kinds.
208 struct GenericPath(F) {
209 @safe:
210 	alias Format = F;
211 
212 	/// vibe-core 1.x compatibility alias
213 	alias Segment2 = Segment;
214 
215 	/** A single path segment.
216 	*/
217 	static struct Segment {
218 		@safe:
219 
220 		private {
221 			string m_encodedName;
222 			char m_separator = 0;
223 		}
224 
225 		/** Constructs a new path segment including an optional trailing
226 			separator.
227 
228 			Params:
229 				name = The raw (unencoded) name of the path segment
230 				separator = Optional trailing path separator (e.g. `'/'`)
231 
232 			Throws:
233 				A `PathValidationException` is thrown if the name contains
234 				characters that are invalid for the path type. In particular,
235 				any path separator characters may not be part of the name.
236 		*/
237 		this(string name, char separator = '\0')
238 		{
239 			import std.algorithm.searching : any;
240 
241 			enforce!PathValidationException(separator == '\0' || Format.isSeparator(separator),
242 				"Invalid path separator.");
243 			auto err = Format.validateDecodedSegment(name);
244 			enforce!PathValidationException(err is null, err);
245 
246 			m_encodedName = Format.encodeSegment(name);
247 			m_separator = separator;
248 		}
249 
250 		/** Constructs a path segment without performing validation.
251 
252 			Note that in debug builds, there are still assertions in place
253 			that verify that the provided values are valid.
254 
255 			Params:
256 				name = The raw (unencoded) name of the path segment
257 				separator = Optional trailing path separator (e.g. `'/'`)
258 		*/
259 		static Segment fromTrustedString(string name, char separator = '\0')
260 		nothrow pure {
261 			import std.algorithm.searching : any;
262 			assert(separator == '\0' || Format.isSeparator(separator));
263 			assert(Format.validateDecodedSegment(name) is null, "Invalid path segment.");
264 			return fromTrustedEncodedString(Format.encodeSegment(name), separator);
265 		}
266 
267 		/** Constructs a path segment without performing validation.
268 
269 			Note that in debug builds, there are still assertions in place
270 			that verify that the provided values are valid.
271 
272 			Params:
273 				encoded_name = The encoded name of the path segment
274 				separator = Optional trailing path separator (e.g. `'/'`)
275 		*/
276 		static Segment fromTrustedEncodedString(string encoded_name, char separator = '\0')
277 		nothrow @nogc pure {
278 			import std.algorithm.searching : any;
279 			import std.utf : byCodeUnit;
280 
281 			assert(separator == '\0' || Format.isSeparator(separator));
282 			assert(!encoded_name.byCodeUnit.any!(c => Format.isSeparator(c)));
283 			assert(Format.validatePath(encoded_name) is null, "Invalid path segment.");
284 
285 			Segment ret;
286 			ret.m_encodedName = encoded_name;
287 			ret.m_separator = separator;
288 			return ret;
289 		}
290 
291 		/** The (file/directory) name of the path segment.
292 
293 			Note: Depending on the path type, this may return a generic range
294 			type instead of `string`. Use `name.to!string` in that
295 			case if you need an actual `string`.
296 		*/
297 		@property auto name()
298 		const nothrow @nogc {
299 			auto ret = Format.decodeSingleSegment(m_encodedName);
300 
301 			static if (is(typeof(ret) == string)) return ret;
302 			else {
303 				static struct R {
304 					private typeof(ret) m_value;
305 
306 					@property bool empty() const { return m_value.empty; }
307 					@property R save() const { return R(m_value.save); }
308 					@property char front() const { return m_value.front; }
309 					@property void popFront() { m_value.popFront(); }
310 					@property char back() const { return m_value.back; }
311 					@property void popBack() { m_value.popBack(); }
312 
313 					string toString()
314 					const @safe nothrow {
315 						import std.conv : to;
316 						try return m_value.save.to!string;
317 						catch (Exception e) assert(false, e.msg);
318 					}
319 				}
320 
321 				return R(ret);
322 			}
323 		}
324 
325 		unittest {
326 			import std.conv : to;
327 			auto path = InetPath("/foo%20bar");
328 			assert(path.head.encodedName == "foo%20bar");
329 			assert(path.head.name.toString() == "foo bar");
330 			assert(path.head.name.to!string == "foo bar");
331 			assert(path.head.name.equal("foo bar"));
332 		}
333 
334 
335 		/// The encoded representation of the path segment name
336 		@property string encodedName() const nothrow @nogc { return m_encodedName; }
337 		/// The trailing separator (e.g. `'/'`) or `'\0'`.
338 		@property char separator() const nothrow @nogc { return m_separator; }
339 		/// ditto
340 		@property void separator(char ch) {
341 			enforce!PathValidationException(ch == '\0' || Format.isSeparator(ch),
342 				"Character is not a valid path separator.");
343 			m_separator = ch;
344 		}
345 		/// Returns `true` $(I iff) the segment has a trailing path separator.
346 		@property bool hasSeparator() const nothrow @nogc { return m_separator != '\0'; }
347 
348 
349 		/** The extension part of the file name.
350 
351 			If the file name contains an extension, this returns a forward range
352 			with the extension including the leading dot. Otherwise an empty
353 			range is returned.
354 
355 			See_also: `stripExtension`
356 		*/
357 		@property auto extension()
358 		const nothrow @nogc {
359 			return .extension(this.name);
360 		}
361 
362 		///
363 		unittest {
364 			assert(PosixPath("/foo/bar.txt").head.extension.equal(".txt"));
365 			assert(PosixPath("/foo/bar").head.extension.equal(""));
366 			assert(PosixPath("/foo/.bar").head.extension.equal(""));
367 			assert(PosixPath("/foo/.bar.txt").head.extension.equal(".txt"));
368 		}
369 
370 
371 		/** Returns the file base name, excluding the extension.
372 
373 			See_also: `extension`
374 		*/
375 		@property auto withoutExtension()
376 		const nothrow @nogc {
377 			return .stripExtension(this.name);
378 		}
379 
380 		///
381 		unittest {
382 			assert(PosixPath("/foo/bar.txt").head.withoutExtension.equal("bar"));
383 			assert(PosixPath("/foo/bar").head.withoutExtension.equal("bar"));
384 			assert(PosixPath("/foo/.bar").head.withoutExtension.equal(".bar"));
385 			assert(PosixPath("/foo/.bar.txt").head.withoutExtension.equal(".bar"));
386 		}
387 
388 
389 		/** Converts the segment to another path type.
390 
391 			The segment name will be re-validated during the conversion. The
392 			separator, if any, will be adopted or replaced by the default
393 			separator of the target path type.
394 
395 			Throws:
396 				A `PathValidationException` is thrown if the segment name cannot
397 				be represented in the target path format.
398 		*/
399 		GenericPath!F.Segment opCast(T : GenericPath!F.Segment, F)()
400 		const {
401 			import std.array : array;
402 
403 			char dsep = '\0';
404 			if (m_separator) {
405 				if (F.isSeparator(m_separator)) dsep = m_separator;
406 				else dsep = F.defaultSeparator;
407 			}
408 			static if (is(typeof(this.name) == string))
409 				string n = this.name;
410 			else
411 				string n = this.name.array;
412 			return GenericPath!F.Segment(n, dsep);
413 		}
414 
415 		/// Compares two path segment names
416 		bool opEquals(Segment other)
417 		const nothrow @nogc {
418 			try return equal(this.name, other.name) && this.hasSeparator == other.hasSeparator;
419 			catch (Exception e) assert(false, e.msg);
420 		}
421 		/// ditto
422 		bool opEquals(string name)
423 		const nothrow @nogc {
424 			import std.utf : byCodeUnit;
425 			try return equal(this.name, name.byCodeUnit);
426 			catch (Exception e) assert(false, e.msg);
427 		}
428 	}
429 
430 	private {
431 		string m_path;
432 	}
433 
434 	/// The default path segment separator character.
435 	enum char defaultSeparator = Format.defaultSeparator;
436 
437 	/** Constructs a path from its string representation.
438 
439 		Throws:
440 			A `PathValidationException` is thrown if the given path string
441 			is not valid.
442 	*/
443 	this(string p)
444 	{
445 		auto err = Format.validatePath(p);
446 		enforce!PathValidationException(err is null, err);
447 		m_path = p;
448 	}
449 
450 	/** Constructs a path from a single path segment.
451 
452 		This is equivalent to calling the range based constructor with a
453 		single-element range.
454 	*/
455 	this(Segment segment)
456 	{
457 		import std.range : only;
458 		this(only(segment));
459 	}
460 
461 	/** Constructs a path from an input range of `Segment`s.
462 
463 		Throws:
464 			Since path segments are pre-validated, this constructor does not
465 			throw an exception.
466 	*/
467 	this(R)(R segments)
468 		if (isInputRange!R && is(ElementType!R : Segment))
469 	{
470 		import std.array : appender;
471 		auto dst = appender!string;
472 		Format.toString(segments, dst);
473 		m_path = dst.data;
474 	}
475 
476 	/** Constructs a path from its string representation.
477 
478 		This is equivalent to calling the string based constructor.
479 	*/
480 	static GenericPath fromString(string p)
481 	{
482 		return GenericPath(p);
483 	}
484 
485 	/** Constructs a path from its string representation, skipping the
486 		validation.
487 
488 		Note that it is required to pass a pre-validated path string
489 		to this function. Debug builds will enforce this with an assertion.
490 	*/
491 	static GenericPath fromTrustedString(string p)
492 	nothrow @nogc {
493 		if (auto val = Format.validatePath(p)) {
494             import std.stdio; debug writeln(p);
495 			assert(false, val);
496         }
497 
498 		GenericPath ret;
499 		ret.m_path = p;
500 		return ret;
501 	}
502 
503 	/// Tests if a certain character is a path segment separator.
504 	static bool isSeparator(dchar ch) { return ch < 0x80 && Format.isSeparator(cast(char)ch); }
505 
506 	/// Tests if the path is represented by an empty string.
507 	@property bool empty() const nothrow @nogc { return m_path.length == 0; }
508 
509 	/// Tests if the path is absolute.
510 	@property bool absolute() const nothrow @nogc { return Format.getAbsolutePrefix(m_path).length > 0; }
511 
512 	/// Determines whether the path ends with a path separator (i.e. represents a folder specifically).
513 	@property bool endsWithSlash() const nothrow @nogc { return m_path.length > 0 && Format.isSeparator(m_path[$-1]); }
514 	/// ditto
515 	@property void endsWithSlash(bool v)
516 	nothrow {
517 		bool ews = this.endsWithSlash;
518 		if (!ews && v) m_path ~= Format.defaultSeparator;
519 		else if (ews && !v) m_path = m_path[0 .. $-1]; // FIXME?: "/test//" -> "/test/"
520 	}
521 
522 	/// vibe-core 1.x compatibility alias
523 	alias bySegment2 = bySegment;
524 
525 	/** Iterates over the individual segments of the path.
526 
527 		Returns a forward range of `Segment`s.
528 	*/
529 	@property auto bySegment()
530 	const {
531 		static struct R {
532 			import std.traits : ReturnType;
533 
534 			private {
535 				string m_path;
536 				Segment m_front;
537 			}
538 
539 			private this(string path)
540 			{
541 				m_path = path;
542 				if (m_path.length) {
543 					auto ap = Format.getAbsolutePrefix(m_path);
544 					if (ap.length && !Format.isSeparator(ap[0]))
545 						m_front = Segment.fromTrustedEncodedString(null, Format.defaultSeparator);
546 					else readFront();
547 				}
548 			}
549 
550 			@property bool empty() const nothrow @nogc { return m_path.length == 0 && m_front == Segment.init; }
551 
552 			@property R save() { return this; }
553 
554 			@property Segment front() { return m_front; }
555 
556 			void popFront()
557 			nothrow {
558 				assert(m_front != Segment.init);
559 				if (m_path.length) readFront();
560 				else m_front = Segment.init;
561 			}
562 
563 			private void readFront()
564 			{
565 				auto n = Format.getFrontNode(m_path);
566 				m_path = m_path[n.length .. $];
567 
568 				char sep = '\0';
569 				if (Format.isSeparator(n[$-1])) {
570 					sep = n[$-1];
571 					n = n[0 .. $-1];
572 				}
573 				m_front = Segment.fromTrustedEncodedString(n, sep);
574 				assert(m_front != Segment.init);
575 			}
576 		}
577 
578 		return R(m_path);
579 	}
580 
581 	///
582 	unittest {
583 		InetPath p = "foo/bar/baz";
584 		assert(p.bySegment.equal([
585 			InetPath.Segment("foo", '/'),
586 			InetPath.Segment("bar", '/'),
587 			InetPath.Segment("baz")
588 		]));
589 	}
590 
591 
592 	/** Iterates over the path by segment, each time returning the sub path
593 		leading to that segment.
594 	*/
595 	@property auto byPrefix()
596 	const nothrow @nogc {
597 		static struct R {
598 			import std.traits : ReturnType;
599 
600 			private {
601 				string m_path;
602 				string m_remainder;
603 			}
604 
605 			private this(string path)
606 			{
607 				m_path = path;
608 				m_remainder = path;
609 				if (m_path.length) {
610 					auto ap = Format.getAbsolutePrefix(m_path);
611 					if (ap.length && !Format.isSeparator(ap[0]))
612 						m_remainder = m_remainder[ap.length .. $];
613 					else popFront();
614 				}
615 			}
616 
617 			@property bool empty() const nothrow @nogc
618 			{
619 				return m_path.length == 0;
620 			}
621 
622 			@property R save() { return this; }
623 
624 			@property GenericPath front()
625 			{
626 				return GenericPath.fromTrustedString(m_path[0 .. $-m_remainder.length]);
627 			}
628 
629 			void popFront()
630 			nothrow {
631 				assert(m_remainder.length > 0 || m_path.length > 0);
632 				if (m_remainder.length) readFront();
633 				else m_path = "";
634 			}
635 
636 			private void readFront()
637 			{
638 				auto n = Format.getFrontNode(m_remainder);
639 				m_remainder = m_remainder[n.length .. $];
640 			}
641 		}
642 
643 		return R(m_path);
644 	}
645 
646 	///
647 	version (none) unittest {
648 		assert(InetPath("foo/bar/baz").byPrefix
649 			.equal([
650 				InetPath("foo/"),
651 				InetPath("foo/bar/"),
652 				InetPath("foo/bar/baz")
653 			]));
654 
655 		assert(InetPath("/foo/bar").byPrefix
656 			.equal([
657 				InetPath("/"),
658 				InetPath("/foo/"),
659 				InetPath("/foo/bar"),
660 			]));
661 	}
662 
663 	// vibe-core 1.x compatibility alias
664 	alias head2 = head;
665 
666 	/// Returns the trailing segment of the path.
667 	@property Segment head()
668 	const @nogc {
669 		auto n = Format.getBackNode(m_path);
670 		char sep = '\0';
671 		if (n.length > 0 && Format.isSeparator(n[$-1])) {
672 			sep = n[$-1];
673 			n = n[0 .. $-1];
674 		}
675 		return Segment.fromTrustedEncodedString(n, sep);
676 	}
677 
678 	/** Determines if the `parentPath` property is valid.
679 	*/
680 	@property bool hasParentPath()
681 	const @nogc {
682 		auto b = Format.getBackNode(m_path);
683 		return b.length < m_path.length;
684 	}
685 
686 	/** Returns a prefix of this path, where the last segment has been dropped.
687 
688 		Throws:
689 			An `Exception` is thrown if this path has no parent path. Use
690 			`hasParentPath` to test this upfront.
691 	*/
692 	@property GenericPath parentPath()
693 	const @nogc {
694 		auto b = Format.getBackNode(m_path);
695 		() @trusted {
696 			static __gshared e = new Exception("Path has no parent path");
697 			if (b.length >= m_path.length) throw e;
698 		} ();
699 		return GenericPath.fromTrustedString(m_path[0 .. $ - b.length]);
700 	}
701 
702 
703 	/** The extension part of the file name pointed to by the path.
704 
705 		If the path is not empty and its head segment has  an extension, this
706 		returns a forward range with the extension including the leading dot.
707 		Otherwise an empty range is returned.
708 
709 		See `Segment.extension` for a full description.
710 
711 		See_also: `Segment.extension`, `Segment.stripExtension`
712 	*/
713 	@property auto fileExtension()
714 	const nothrow @nogc {
715 		if (this.empty) return typeof(this.head.extension).init;
716 		return this.head.extension;
717 	}
718 
719 
720 	/** Returns the normalized form of the path.
721 
722 		See `normalize` for a full description.
723 	*/
724 	@property GenericPath normalized()
725 	const {
726 		GenericPath ret = this;
727 		ret.normalize();
728 		return ret;
729 	}
730 
731 	unittest {
732 		assert(PosixPath("foo/../bar").normalized == PosixPath("bar"));
733 		assert(PosixPath("foo//./bar/../baz").normalized == PosixPath("foo/baz"));
734 	}
735 
736 
737 	/** Removes any redundant path segments and replaces all separators by the
738 		default one.
739 
740 		The resulting path representation is suitable for basic semantic
741 		comparison to other normalized paths.
742 
743 		Note that there are still ways for different normalized paths to
744 		represent the same file. Examples of this are the tilde shortcut to the
745 		home directory on Unix and Linux operating systems, symbolic or hard
746 		links, and possibly environment variables are examples of this.
747 
748 		Throws:
749 			Throws an `Exception` if an absolute path contains parent directory
750 			segments ("..") that lead to a path that is a parent path of the
751 			root path.
752 	*/
753 	void normalize()
754 	{
755 		import std.array : appender, join;
756 
757 		Segment[] newnodes;
758 		bool got_non_sep = false;
759 		foreach (n; this.bySegment) {
760 			if (n.hasSeparator) n.separator = Format.defaultSeparator;
761 			if (!got_non_sep) {
762 				if (n.encodedName == "") newnodes ~= n;
763 				else got_non_sep = true;
764 			}
765 			switch (n.encodedName) {
766 				default: newnodes ~= n; break;
767 				case "", ".": break;
768 				case "..":
769 					enforce(!this.absolute || newnodes.length > 0, "Path goes below root node.");
770 					if (newnodes.length > 0 && newnodes[$-1].encodedName != "..") newnodes = newnodes[0 .. $-1];
771 					else newnodes ~= n;
772 					break;
773 			}
774 		}
775 
776 		auto dst = appender!string;
777 		Format.toString(newnodes, dst);
778 		m_path = dst.data;
779 	}
780 
781 	///
782 	unittest {
783 		auto path = WindowsPath("C:\\test/foo/./bar///../baz");
784 		path.normalize();
785 		assert(path.toString() == "C:\\test\\foo\\baz", path.toString());
786 
787 		path = WindowsPath("foo/../../bar/");
788 		path.normalize();
789 		assert(path.toString() == "..\\bar\\");
790 	}
791 
792 	/// Returns the string representation of the path.
793 	string toString() const nothrow @nogc { return m_path; }
794 
795 	/// Computes a hash sum, enabling storage within associative arrays.
796 	size_t toHash() const nothrow @trusted
797 	{
798 		try return typeid(string).getHash(&m_path);
799 		catch (Exception e) assert(false, "getHash for string throws!?");
800 	}
801 
802 	/** Compares two path objects.
803 
804 		Note that the exact string representation of the two paths will be
805 		compared. To get a basic semantic comparison, the paths must be
806 		normalized first.
807 	*/
808 	bool opEquals(GenericPath other) const @nogc { return this.m_path == other.m_path; }
809 
810 	/** Converts the path to a different path format.
811 
812 		Throws:
813 			A `PathValidationException` will be thrown if the path is not
814 			representable in the requested path format. This can happen
815 			especially when converting Posix or Internet paths to windows paths,
816 			since Windows paths cannot contain a number of characters that the
817 			other representations can, in theory.
818 	*/
819 	P opCast(P)() const if (isInstanceOf!(.GenericPath, P)) {
820 		static if (is(P == GenericPath)) return this;
821 		else return P(this.bySegment.map!(n => cast(P.Segment)n));
822 	}
823 
824 	/** Concatenates two paths.
825 
826 		The right hand side must represent a relative path.
827 	*/
828 	GenericPath opBinary(string op : "~")(string subpath) const { return this ~ GenericPath(subpath); }
829 	/// ditto
830 	GenericPath opBinary(string op : "~")(Segment subpath) const { return this ~ GenericPath(subpath); }
831 	/// ditto
832 	GenericPath opBinary(string op : "~", F)(GenericPath!F.Segment subpath) const { return this ~ cast(Segment)(subpath); }
833 	/// ditto
834 	GenericPath opBinary(string op : "~")(GenericPath subpath) const nothrow {
835 		assert(!subpath.absolute || m_path.length == 0, "Cannot append absolute path.");
836 		if (endsWithSlash || empty) return GenericPath.fromTrustedString(m_path ~ subpath.m_path);
837 		else return GenericPath.fromTrustedString(m_path ~ Format.defaultSeparator ~ subpath.m_path);
838 	}
839 	/// ditto
840 	GenericPath opBinary(string op : "~", F)(GenericPath!F subpath) const if (!is(F == Format)) { return this ~ cast(GenericPath)subpath; }
841 	/// ditto
842 	GenericPath opBinary(string op : "~", R)(R entries) const nothrow
843 		if (isInputRange!R && is(ElementType!R : Segment))
844 	{
845 		return this ~ GenericPath(entries);
846 	}
847 
848 	/// Appends a relative path to this path.
849 	void opOpAssign(string op : "~", T)(T op) { this = this ~ op; }
850 
851 	/** Tests whether the given path is a prefix of this path.
852 
853 		Any path separators will be ignored during the comparison.
854 	*/
855 	bool startsWith(GenericPath prefix)
856 	const nothrow {
857 		return bySegment.map!(n => n.name).startsWith(prefix.bySegment.map!(n => n.name));
858 	}
859 }
860 
861 unittest {
862 	assert(PosixPath("hello/world").bySegment.equal([PosixPath.Segment("hello",'/'), PosixPath.Segment("world")]));
863 	assert(PosixPath("/hello/world/").bySegment.equal([PosixPath.Segment("",'/'), PosixPath.Segment("hello",'/'), PosixPath.Segment("world",'/')]));
864 	assert(PosixPath("hello\\world").bySegment.equal([PosixPath.Segment("hello\\world")]));
865 	assert(WindowsPath("hello/world").bySegment.equal([WindowsPath.Segment("hello",'/'), WindowsPath.Segment("world")]));
866 	assert(WindowsPath("/hello/world/").bySegment.equal([WindowsPath.Segment("",'/'), WindowsPath.Segment("hello",'/'), WindowsPath.Segment("world",'/')]));
867 	assert(WindowsPath("hello\\w/orld").bySegment.equal([WindowsPath.Segment("hello",'\\'), WindowsPath.Segment("w",'/'), WindowsPath.Segment("orld")]));
868 	assert(WindowsPath("hello/w\\orld").bySegment.equal([WindowsPath.Segment("hello",'/'), WindowsPath.Segment("w",'\\'), WindowsPath.Segment("orld")]));
869 
870     version (none) {
871 	assert(PosixPath("hello/world").byPrefix.equal([PosixPath("hello/"), PosixPath("hello/world")]));
872 	assert(PosixPath("/hello/world/").byPrefix.equal([PosixPath("/"), PosixPath("/hello/"), PosixPath("/hello/world/")]));
873 	assert(WindowsPath("C:\\Windows").byPrefix.equal([WindowsPath("C:\\"), WindowsPath("C:\\Windows")]));
874     }
875 }
876 
877 unittest
878 {
879 	{
880 		auto dotpath = "/test/../test2/././x/y";
881 		auto dotpathp = PosixPath(dotpath);
882 		assert(dotpathp.toString() == "/test/../test2/././x/y");
883 		dotpathp.normalize();
884 		assert(dotpathp.toString() == "/test2/x/y", dotpathp.toString());
885 	}
886 
887 	{
888 		auto dotpath = "/test/..////test2//./x/y";
889 		auto dotpathp = PosixPath(dotpath);
890 		assert(dotpathp.toString() == "/test/..////test2//./x/y");
891 		dotpathp.normalize();
892 		assert(dotpathp.toString() == "/test2/x/y");
893 	}
894 
895 	assert(WindowsPath("C:\\Windows").absolute);
896 	assert((cast(InetPath)WindowsPath("C:\\Windows")).toString() == "/C:/Windows");
897 	assert((WindowsPath("C:\\Windows") ~ InetPath("test/this")).toString() == "C:\\Windows\\test/this");
898 	assert(InetPath("/C:/Windows").absolute);
899 	assert((cast(WindowsPath)InetPath("/C:/Windows")).toString() == "C:/Windows");
900 	assert((InetPath("/C:/Windows") ~ WindowsPath("test\\this")).toString() == "/C:/Windows/test/this");
901 	assert((InetPath("") ~ WindowsPath("foo\\bar")).toString() == "foo/bar");
902 	assert((cast(InetPath)WindowsPath("C:\\Windows\\")).toString() == "/C:/Windows/");
903 
904 	assert(NativePath("").empty);
905 
906 	assert(PosixPath("/") ~ NativePath("foo/bar") == PosixPath("/foo/bar"));
907 	assert(PosixPath("") ~ NativePath("foo/bar") == PosixPath("foo/bar"));
908 	assert(PosixPath("foo") ~ NativePath("bar") == PosixPath("foo/bar"));
909 	assert(PosixPath("foo/") ~ NativePath("bar") == PosixPath("foo/bar"));
910 
911 	{
912 		auto unc = "\\\\server\\share\\path";
913 		auto uncp = WindowsPath(unc);
914 		assert(uncp.absolute);
915 		uncp.normalize();
916 		version(Windows) assert(uncp.toNativeString() == unc);
917 		assert(uncp.absolute);
918 		assert(!uncp.endsWithSlash);
919 	}
920 
921 	{
922 		auto abspath = "/test/path/";
923 		auto abspathp = PosixPath(abspath);
924 		assert(abspathp.toString() == abspath);
925 		version(Windows) {} else assert(abspathp.toNativeString() == abspath);
926 		assert(abspathp.absolute);
927 		assert(abspathp.endsWithSlash);
928 		alias S = PosixPath.Segment;
929 		assert(abspathp.bySegment.equal([S("", '/'), S("test", '/'), S("path", '/')]));
930 	}
931 
932 	{
933 		auto relpath = "test/path/";
934 		auto relpathp = PosixPath(relpath);
935 		assert(relpathp.toString() == relpath);
936 		version(Windows) assert(relpathp.toNativeString() == "test/path/");
937 		else assert(relpathp.toNativeString() == relpath);
938 		assert(!relpathp.absolute);
939 		assert(relpathp.endsWithSlash);
940 		alias S = PosixPath.Segment;
941 		assert(relpathp.bySegment.equal([S("test", '/'), S("path", '/')]));
942 	}
943 
944 	{
945 		auto winpath = "C:\\windows\\test";
946 		auto winpathp = WindowsPath(winpath);
947 		assert(winpathp.toString() == "C:\\windows\\test");
948 		assert((cast(PosixPath)winpathp).toString() == "/C:/windows/test", (cast(PosixPath)winpathp).toString());
949 		version(Windows) assert(winpathp.toNativeString() == winpath);
950 		else assert(winpathp.toNativeString() == "/C:/windows/test", winpathp.toNativeString());
951 		assert(winpathp.absolute);
952 		assert(!winpathp.endsWithSlash);
953 		alias S = WindowsPath.Segment;
954 		assert(winpathp.bySegment.equal([S("", '/'), S("C:", '\\'), S("windows", '\\'), S("test")]));
955 	}
956 }
957 
958 @safe unittest {
959 	import std.array : appender;
960 	auto app = appender!(PosixPath[]);
961 	void test1(PosixPath p) { app.put(p); }
962 	void test2(PosixPath[] ps) { app.put(ps); }
963 	//void test3(const(PosixPath) p) { app.put(p); } // DMD issue 17251
964 	//void test4(const(PosixPath)[] ps) { app.put(ps); }
965 }
966 
967 unittest {
968 	import std.exception : assertThrown, assertNotThrown;
969 
970 	assertThrown!PathValidationException(WindowsPath.Segment("foo/bar"));
971 	assertThrown!PathValidationException(PosixPath.Segment("foo/bar"));
972 	assertNotThrown!PathValidationException(InetPath.Segment("foo/bar"));
973 
974 	auto p = InetPath("/foo%2fbar/");
975 	import std.conv : to;
976 	assert(p.bySegment.equal([InetPath.Segment("",'/'), InetPath.Segment("foo/bar",'/')]), p.bySegment.to!string);
977 	p ~= InetPath.Segment("baz/bam");
978 	assert(p.toString() == "/foo%2fbar/baz%2Fbam", p.toString);
979 }
980 
981 unittest {
982 	assert(!PosixPath("").hasParentPath);
983 	assert(!PosixPath("/").hasParentPath);
984 	assert(!PosixPath("foo\\bar").hasParentPath);
985 	assert(PosixPath("foo/bar").parentPath.toString() == "foo/");
986 	assert(PosixPath("./foo").parentPath.toString() == "./");
987 	assert(PosixPath("./foo").parentPath.toString() == "./");
988 
989 	assert(!WindowsPath("").hasParentPath);
990 	assert(!WindowsPath("/").hasParentPath);
991 	assert(WindowsPath("foo\\bar").parentPath.toString() == "foo\\");
992 	assert(WindowsPath("foo/bar").parentPath.toString() == "foo/");
993 	assert(WindowsPath("./foo").parentPath.toString() == "./");
994 	assert(WindowsPath("./foo").parentPath.toString() == "./");
995 
996 	assert(!InetPath("").hasParentPath);
997 	assert(!InetPath("/").hasParentPath);
998 	assert(InetPath("foo/bar").parentPath.toString() == "foo/");
999 	assert(InetPath("foo/bar%2Fbaz").parentPath.toString() == "foo/");
1000 	assert(InetPath("./foo").parentPath.toString() == "./");
1001 	assert(InetPath("./foo").parentPath.toString() == "./");
1002 }
1003 
1004 unittest {
1005 	assert(WindowsPath([WindowsPath.Segment("foo"), WindowsPath.Segment("bar")]).toString() == "foo\\bar");
1006 }
1007 
1008 unittest {
1009 	assert(WindowsPath([WindowsPath.Segment("foo"), WindowsPath.Segment("bar")]).toString() == "foo\\bar");
1010 }
1011 
1012 /// Thrown when an invalid string representation of a path is detected.
1013 class PathValidationException : Exception {
1014 	this(string text, string file = __FILE__, size_t line = cast(size_t)__LINE__, Throwable next = null)
1015 		pure nothrow @nogc @safe
1016 	{
1017 		super(text, file, line, next);
1018 	}
1019 }
1020 
1021 /** Implements Windows path semantics.
1022 
1023 	See_also: `WindowsPath`
1024 */
1025 struct WindowsPathFormat {
1026 	static void toString(I, O)(I segments, O dst)
1027 		if (isInputRange!I && isOutputRange!(O, char))
1028 	{
1029 		char sep(char s) { return isSeparator(s) ? s : defaultSeparator; }
1030 
1031 		if (segments.empty) return;
1032 
1033 		if (segments.front.name == "" && segments.front.separator) {
1034 			auto s = segments.front.separator;
1035 			segments.popFront();
1036 			if (segments.empty || !segments.front.name.endsWith(":"))
1037 				dst.put(sep(s));
1038 		}
1039 
1040 		char lastsep = '\0';
1041 		bool first = true;
1042 		foreach (s; segments) {
1043 			if (!first || lastsep) dst.put(sep(lastsep));
1044 			else first = false;
1045 			dst.put(s.name);
1046 			lastsep = s.separator;
1047 		}
1048 		if (lastsep) dst.put(sep(lastsep));
1049 	}
1050 
1051 	unittest {
1052 		import std.array : appender;
1053 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1054 		string str(Segment[] segs...) { auto ret = appender!string; toString(segs, ret); return ret.data; }
1055 
1056 		assert(str() == "");
1057 		assert(str(Segment("",'/')) == "/");
1058 		assert(str(Segment("",'/'), Segment("foo")) == "/foo");
1059 		assert(str(Segment("",'\\')) == "\\");
1060 		assert(str(Segment("foo",'/'), Segment("bar",'/')) == "foo/bar/");
1061 		assert(str(Segment("",'/'), Segment("foo",'\0')) == "/foo");
1062 		assert(str(Segment("",'\\'), Segment("foo",'\\')) == "\\foo\\");
1063 		assert(str(Segment("f oo")) == "f oo");
1064 		assert(str(Segment("",'\\'), Segment("C:")) == "C:");
1065 		assert(str(Segment("",'\\'), Segment("C:", '/')) == "C:/");
1066 		assert(str(Segment("foo",'\\'), Segment("C:")) == "foo\\C:");
1067 		assert(str(Segment("foo"), Segment("bar")) == "foo\\bar");
1068 	}
1069 
1070 @safe nothrow pure:
1071 	enum defaultSeparator = '\\';
1072 
1073 	static bool isSeparator(dchar ch)
1074 	@nogc {
1075 		return ch == '\\' || ch == '/';
1076 	}
1077 
1078 	static string getAbsolutePrefix(string path)
1079 	@nogc {
1080 		if (!path.length) return null;
1081 
1082 		if (isSeparator(path[0])) {
1083 			return path[0 .. 1];
1084 		}
1085 
1086 		foreach (i; 1 .. path.length)
1087 			if (isSeparator(path[i])) {
1088 				if (path[i-1] == ':') return path[0 .. i+1];
1089 				break;
1090 			}
1091 
1092 		return path[$-1] == ':' ? path : null;
1093 	}
1094 
1095 	unittest {
1096 		assert(getAbsolutePrefix("test") == "");
1097 		assert(getAbsolutePrefix("test/") == "");
1098 		assert(getAbsolutePrefix("/test") == "/");
1099 		assert(getAbsolutePrefix("\\test") == "\\");
1100 		assert(getAbsolutePrefix("C:\\") == "C:\\");
1101 		assert(getAbsolutePrefix("C:") == "C:");
1102 		assert(getAbsolutePrefix("C:\\test") == "C:\\");
1103 		assert(getAbsolutePrefix("C:\\test\\") == "C:\\");
1104 		assert(getAbsolutePrefix("C:/") == "C:/");
1105 		assert(getAbsolutePrefix("C:/test") == "C:/");
1106 		assert(getAbsolutePrefix("C:/test/") == "C:/");
1107 		assert(getAbsolutePrefix("\\\\server") == "\\");
1108 		assert(getAbsolutePrefix("\\\\server\\") == "\\");
1109 		assert(getAbsolutePrefix("\\\\.\\") == "\\");
1110 		assert(getAbsolutePrefix("\\\\?\\") == "\\");
1111 	}
1112 
1113 	static string getFrontNode(string path)
1114 	@nogc {
1115 		foreach (i; 0 .. path.length)
1116 			if (isSeparator(path[i]))
1117 				return path[0 .. i+1];
1118 		return path;
1119 	}
1120 
1121 	unittest {
1122 		assert(getFrontNode("") == "");
1123 		assert(getFrontNode("/bar") == "/");
1124 		assert(getFrontNode("foo/bar") == "foo/");
1125 		assert(getFrontNode("foo/") == "foo/");
1126 		assert(getFrontNode("foo") == "foo");
1127 		assert(getFrontNode("\\bar") == "\\");
1128 		assert(getFrontNode("foo\\bar") == "foo\\");
1129 		assert(getFrontNode("foo\\") == "foo\\");
1130 	}
1131 
1132 	static string getBackNode(string path)
1133 	@nogc {
1134 		if (!path.length) return path;
1135 		foreach_reverse (i; 0 .. path.length-1)
1136 			if (isSeparator(path[i]))
1137 				return path[i+1 .. $];
1138 		return path;
1139 	}
1140 
1141 	unittest {
1142 		assert(getBackNode("") == "");
1143 		assert(getBackNode("/bar") == "bar");
1144 		assert(getBackNode("foo/bar") == "bar");
1145 		assert(getBackNode("foo/") == "foo/");
1146 		assert(getBackNode("foo") == "foo");
1147 		assert(getBackNode("\\bar") == "bar");
1148 		assert(getBackNode("foo\\bar") == "bar");
1149 		assert(getBackNode("foo\\") == "foo\\");
1150 	}
1151 
1152 	static string decodeSingleSegment(string segment)
1153 	@nogc {
1154 		assert(segment.length == 0 || segment[$-1] != '/');
1155 		return segment;
1156 	}
1157 
1158 	unittest {
1159 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1160 		assert(decodeSingleSegment("foo") == "foo");
1161 		assert(decodeSingleSegment("fo%20o") == "fo%20o");
1162 		assert(decodeSingleSegment("C:") == "C:");
1163 		assert(decodeSingleSegment("bar:") == "bar:");
1164 	}
1165 
1166 	static string validatePath(string path)
1167 	@nogc {
1168 		import std.algorithm.comparison : among;
1169 
1170 		// skip UNC prefix
1171 		if (path.startsWith("\\\\")) {
1172 			path = path[2 .. $];
1173 			while (path.length && !isSeparator(path[0])) {
1174 				if (path[0] < 32 || path[0].among('<', '>', '|'))
1175 					return "Invalid character in UNC host name.";
1176 				path = path[1 .. $];
1177 			}
1178 			if (path.length) path = path[1 .. $];
1179 		}
1180 
1181 		// stricter validation for the rest
1182 		bool had_sep = false;
1183 		foreach (i, char c; path) {
1184 			if (c < 32 || c.among!('<', '>', '|', '?'))
1185 				return "Invalid character in path.";
1186 			if (isSeparator(c)) had_sep = true;
1187 			else if (c == ':' && (had_sep || i+1 < path.length && !isSeparator(path[i+1])))
1188 				return "Colon in path that is not part of a drive name.";
1189 
1190 		}
1191 		return null;
1192 	}
1193 
1194 	static string validateDecodedSegment(string segment)
1195 	@nogc {
1196 		auto pe = validatePath(segment);
1197 		if (pe) return pe;
1198 		foreach (char c; segment)
1199 			if (isSeparator(c))
1200 				return "Path segment contains separator character.";
1201 		return null;
1202 	}
1203 
1204 	unittest {
1205 		assert(validatePath("c:\\foo") is null);
1206 		assert(validatePath("\\\\?\\c:\\foo") is null);
1207 		assert(validatePath("//?\\c:\\foo") !is null);
1208 		assert(validatePath("-foo/bar\\*\\baz") is null);
1209 		assert(validatePath("foo\0bar") !is null);
1210 		assert(validatePath("foo\tbar") !is null);
1211 		assert(validatePath("\\c:\\foo") !is null);
1212 		assert(validatePath("c:d\\foo") !is null);
1213 		assert(validatePath("foo\\b:ar") !is null);
1214 		assert(validatePath("foo\\bar:\\baz") !is null);
1215 	}
1216 
1217 	static string encodeSegment(string segment)
1218 	{
1219 		assert(segment.length == 0 || segment[$-1] != '/');
1220 		return segment;
1221 	}
1222 }
1223 
1224 
1225 /** Implements Unix/Linux path semantics.
1226 
1227 	See_also: `WindowsPath`
1228 */
1229 struct PosixPathFormat {
1230 	static void toString(I, O)(I segments, O dst)
1231 	{
1232 		char lastsep = '\0';
1233 		bool first = true;
1234 		foreach (s; segments) {
1235 			if (!first || lastsep) dst.put('/');
1236 			else first = false;
1237 			dst.put(s.name);
1238 			lastsep = s.separator;
1239 		}
1240 		if (lastsep) dst.put('/');
1241 	}
1242 
1243 	unittest {
1244 		import std.array : appender;
1245 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1246 		string str(Segment[] segs...) { auto ret = appender!string; toString(segs, ret); return ret.data; }
1247 
1248 		assert(str() == "");
1249 		assert(str(Segment("",'/')) == "/");
1250 		assert(str(Segment("foo",'/'), Segment("bar",'/')) == "foo/bar/");
1251 		assert(str(Segment("",'/'), Segment("foo",'\0')) == "/foo");
1252 		assert(str(Segment("",'\\'), Segment("foo",'\\')) == "/foo/");
1253 		assert(str(Segment("f oo")) == "f oo");
1254 		assert(str(Segment("foo"), Segment("bar")) == "foo/bar");
1255 	}
1256 
1257 @safe nothrow pure:
1258 	enum defaultSeparator = '/';
1259 
1260 	static bool isSeparator(dchar ch)
1261 	@nogc {
1262 		return ch == '/';
1263 	}
1264 
1265 	static string getAbsolutePrefix(string path)
1266 	@nogc {
1267 		if (path.length > 0 && path[0] == '/')
1268 			return path[0 .. 1];
1269 		return null;
1270 	}
1271 
1272 	unittest {
1273 		assert(getAbsolutePrefix("/") == "/");
1274 		assert(getAbsolutePrefix("/test") == "/");
1275 		assert(getAbsolutePrefix("/test/") == "/");
1276 		assert(getAbsolutePrefix("test/") == "");
1277 		assert(getAbsolutePrefix("") == "");
1278 		assert(getAbsolutePrefix("./") == "");
1279 	}
1280 
1281 	static string getFrontNode(string path)
1282 	@nogc {
1283 		import std.string : indexOf;
1284 		auto idx = path.indexOf('/');
1285 		return idx < 0 ? path : path[0 .. idx+1];
1286 	}
1287 
1288 	unittest {
1289 		assert(getFrontNode("") == "");
1290 		assert(getFrontNode("/bar") == "/");
1291 		assert(getFrontNode("foo/bar") == "foo/");
1292 		assert(getFrontNode("foo/") == "foo/");
1293 		assert(getFrontNode("foo") == "foo");
1294 	}
1295 
1296 	static string getBackNode(string path)
1297 	@nogc {
1298 		if (!path.length) return path;
1299 		foreach_reverse (i; 0 .. path.length-1)
1300 			if (path[i] == '/')
1301 				return path[i+1 .. $];
1302 		return path;
1303 	}
1304 
1305 	unittest {
1306 		assert(getBackNode("") == "");
1307 		assert(getBackNode("/bar") == "bar");
1308 		assert(getBackNode("foo/bar") == "bar");
1309 		assert(getBackNode("foo/") == "foo/");
1310 		assert(getBackNode("foo") == "foo");
1311 	}
1312 
1313 	static string validatePath(string path)
1314 	@nogc {
1315 		foreach (char c; path)
1316 			if (c == '\0')
1317 				return "Invalid NUL character in file name";
1318 		return null;
1319 	}
1320 
1321 	static string validateDecodedSegment(string segment)
1322 	@nogc {
1323 		auto pe = validatePath(segment);
1324 		if (pe) return pe;
1325 		foreach (char c; segment)
1326 			if (isSeparator(c))
1327 				return "Path segment contains separator character.";
1328 		return null;
1329 	}
1330 
1331 	unittest {
1332 		assert(validatePath("-foo/bar*/baz?") is null);
1333 		assert(validatePath("foo\0bar") !is null);
1334 	}
1335 
1336 	static string decodeSingleSegment(string segment)
1337 	@nogc {
1338 		assert(segment.length == 0 || segment[$-1] != '/');
1339 		return segment;
1340 	}
1341 
1342 	unittest {
1343 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1344 		assert(decodeSingleSegment("foo") == "foo");
1345 		assert(decodeSingleSegment("fo%20o\\") == "fo%20o\\");
1346 	}
1347 
1348 	static string encodeSegment(string segment)
1349 	{
1350 		assert(segment.length == 0 || segment[$-1] != '/');
1351 		return segment;
1352 	}
1353 }
1354 
1355 
1356 /** Implements URI/Internet path semantics.
1357 
1358 	See_also: `WindowsPath`
1359 */
1360 struct InetPathFormat {
1361 	static void toString(I, O)(I segments, O dst)
1362 	{
1363 		char lastsep = '\0';
1364 		bool first = true;
1365 		foreach (e; segments) {
1366 			if (!first || lastsep) dst.put('/');
1367 			else first = false;
1368 			static if (is(typeof(e.encodedName)))
1369 				dst.put(e.encodedName);
1370 			else encodeSegment(dst, e.name);
1371 			lastsep = e.separator;
1372 		}
1373 		if (lastsep) dst.put('/');
1374 	}
1375 
1376 	unittest {
1377 		import std.array : appender;
1378 		struct Segment { string name; char separator = 0; static Segment fromTrustedString(string str, char sep = 0) pure nothrow @nogc { return Segment(str, sep); }}
1379 		string str(Segment[] segs...) { auto ret = appender!string; toString(segs, ret); return ret.data; }
1380 		assert(str() == "");
1381 		assert(str(Segment("",'/')) == "/");
1382 		assert(str(Segment("foo",'/'), Segment("bar",'/')) == "foo/bar/");
1383 		assert(str(Segment("",'/'), Segment("foo",'\0')) == "/foo");
1384 		assert(str(Segment("",'\\'), Segment("foo",'\\')) == "/foo/");
1385 		assert(str(Segment("f oo")) == "f%20oo");
1386 		assert(str(Segment("foo"), Segment("bar")) == "foo/bar");
1387 	}
1388 
1389 @safe pure nothrow:
1390 	enum defaultSeparator = '/';
1391 
1392 	static bool isSeparator(dchar ch)
1393 	@nogc {
1394 		return ch == '/';
1395 	}
1396 
1397 	static string getAbsolutePrefix(string path)
1398 	@nogc {
1399 		if (path.length > 0 && path[0] == '/')
1400 			return path[0 .. 1];
1401 		return null;
1402 	}
1403 
1404 	unittest {
1405 		assert(getAbsolutePrefix("/") == "/");
1406 		assert(getAbsolutePrefix("/test") == "/");
1407 		assert(getAbsolutePrefix("/test/") == "/");
1408 		assert(getAbsolutePrefix("test/") == "");
1409 		assert(getAbsolutePrefix("") == "");
1410 		assert(getAbsolutePrefix("./") == "");
1411 	}
1412 
1413 	static string getFrontNode(string path)
1414 	@nogc {
1415 		import std.string : indexOf;
1416 		auto idx = path.indexOf('/');
1417 		return idx < 0 ? path : path[0 .. idx+1];
1418 	}
1419 
1420 	unittest {
1421 		assert(getFrontNode("") == "");
1422 		assert(getFrontNode("/bar") == "/");
1423 		assert(getFrontNode("foo/bar") == "foo/");
1424 		assert(getFrontNode("foo/") == "foo/");
1425 		assert(getFrontNode("foo") == "foo");
1426 	}
1427 
1428 	static string getBackNode(string path)
1429 	@nogc {
1430 		import std.string : lastIndexOf;
1431 
1432 		if (!path.length) return path;
1433 		ptrdiff_t idx;
1434 		try idx = path[0 .. $-1].lastIndexOf('/');
1435 		catch (Exception e) assert(false, e.msg);
1436 		if (idx >= 0) return path[idx+1 .. $];
1437 		return path;
1438 	}
1439 
1440 	unittest {
1441 		assert(getBackNode("") == "");
1442 		assert(getBackNode("/bar") == "bar");
1443 		assert(getBackNode("foo/bar") == "bar");
1444 		assert(getBackNode("foo/") == "foo/");
1445 		assert(getBackNode("foo") == "foo");
1446 	}
1447 
1448 	static string validatePath(string path)
1449 	@nogc {
1450 		for (size_t i = 0; i < path.length; i++) {
1451 			if (isAsciiAlphaNum(path[i]))
1452 				continue;
1453 
1454 			switch (path[i]) {
1455 				default:
1456 					return "Invalid character in internet path.";
1457 				// unreserved
1458 				case '-', '.', '_', '~':
1459 				// subdelims
1460 				case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
1461             	// additional delims
1462             	case ':', '@':
1463             	// segment delimiter
1464             	case '/':
1465             		break;
1466             	case '%': // pct encoding
1467             		if (path.length < i+3)
1468             			return "Unterminated percent encoding sequence in internet path.";
1469             		foreach (j; 0 .. 2) {
1470             			switch (path[++i]) {
1471             				default: return "Invalid percent encoding sequence in internet path.";
1472             				case '0': .. case '9':
1473             				case 'a': .. case 'f':
1474             				case 'A': .. case 'F':
1475             					break;
1476             			}
1477             		}
1478             		break;
1479 			}
1480 		}
1481 		return null;
1482 	}
1483 
1484 	static string validateDecodedSegment(string seg)
1485 	@nogc {
1486 		return null;
1487 	}
1488 
1489 	unittest {
1490 		assert(validatePath("") is null);
1491 		assert(validatePath("/") is null);
1492 		assert(validatePath("/test") is null);
1493 		assert(validatePath("test") is null);
1494 		assert(validatePath("/C:/test") is null);
1495 		assert(validatePath("/test%ab") is null);
1496 		assert(validatePath("/test%ag") !is null);
1497 		assert(validatePath("/test%a") !is null);
1498 		assert(validatePath("/test%") !is null);
1499 		assert(validatePath("/test§") !is null);
1500 		assert(validatePath("föö") !is null);
1501 	}
1502 
1503 	static auto decodeSingleSegment(string segment)
1504 	@nogc {
1505 		import std.string : indexOf;
1506 
1507 		static int hexDigit(char ch) @safe nothrow @nogc {
1508 			assert(ch >= '0' && ch <= '9' || ch >= 'A' && ch <= 'F' || ch >= 'a' && ch <= 'f');
1509 			if (ch >= '0' && ch <= '9') return ch - '0';
1510 			else if (ch >= 'a' && ch <= 'f') return ch - 'a' + 10;
1511 			else return ch - 'A' + 10;
1512 		}
1513 
1514 		static struct R {
1515 			@safe pure nothrow @nogc:
1516 
1517 			private {
1518 				string m_str;
1519 			}
1520 
1521 			this(string s)
1522 			{
1523 				m_str = s;
1524 			}
1525 
1526 			@property bool empty() const { return m_str.length == 0; }
1527 
1528 			@property R save() const { return this; }
1529 
1530 			@property char front()
1531 			const {
1532 				auto ch = m_str[0];
1533 				if (ch != '%') return ch;
1534 
1535 				auto a = m_str[1];
1536 				auto b = m_str[2];
1537 				return cast(char)(16 * hexDigit(a) + hexDigit(b));
1538 			}
1539 
1540 			@property void popFront()
1541 			{
1542 				assert(!empty);
1543 				if (m_str[0] == '%') m_str = m_str[3 .. $];
1544 				else m_str = m_str[1 .. $];
1545 			}
1546 
1547 			@property char back()
1548 			const {
1549 				if (m_str.length >= 3 && m_str[$-3] == '%') {
1550 					auto a = m_str[$-2];
1551 					auto b = m_str[$-1];
1552 					return cast(char)(16 * hexDigit(a) + hexDigit(b));
1553 				} else return m_str[$-1];
1554 			}
1555 
1556 			void popBack()
1557 			{
1558 				assert(!empty);
1559 				if (m_str.length >= 3 && m_str[$-3] == '%') m_str = m_str[0 .. $-3];
1560 				else m_str = m_str[0 .. $-1];
1561 			}
1562 		}
1563 
1564 		return R(segment);
1565 	}
1566 
1567 	unittest {
1568 		import std.range : retro;
1569 
1570 		scope (failure) assert(false);
1571 
1572 		assert(decodeSingleSegment("foo").equal("foo"));
1573 		assert(decodeSingleSegment("fo%20o\\").equal("fo o\\"));
1574 		assert(decodeSingleSegment("foo%20").equal("foo "));
1575 		assert(decodeSingleSegment("foo").retro.equal("oof"));
1576 		assert(decodeSingleSegment("fo%20o\\").retro.equal("\\o of"));
1577 		assert(decodeSingleSegment("foo%20").retro.equal(" oof"));
1578 	}
1579 
1580 
1581 	static string encodeSegment(string segment)
1582 	{
1583 		import std.array : appender;
1584 
1585 		foreach (i, char c; segment) {
1586 			if (isAsciiAlphaNum(c)) continue;
1587 			switch (c) {
1588 				default:
1589 					auto ret = appender!string;
1590 					ret.put(segment[0 .. i]);
1591 					encodeSegment(ret, segment[i .. $]);
1592 					return ret.data;
1593 				case '-', '.', '_', '~':
1594 				case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
1595         		case ':', '@':
1596 					break;
1597 			}
1598 		}
1599 
1600 		return segment;
1601 	}
1602 
1603 	unittest {
1604 		assert(encodeSegment("foo") == "foo");
1605 		assert(encodeSegment("foo bar") == "foo%20bar");
1606 	}
1607 
1608 	static void encodeSegment(R)(ref R dst, string segment)
1609 	{
1610 		static immutable char[16] digit = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'];
1611 
1612 		foreach (char c; segment) {
1613 			switch (c) {
1614 				default:
1615 					dst.put('%');
1616 					dst.put(digit[uint(c) / 16]);
1617 					dst.put(digit[uint(c) % 16]);
1618 					break;
1619 				case 'a': .. case 'z':
1620 				case 'A': .. case 'Z':
1621 				case '0': .. case '9':
1622 				case '-', '.', '_', '~':
1623 				case '!', '$', '&', '\'', '(', ')', '*', '+', ',', ';', '=':
1624         		case ':', '@':
1625 					dst.put(c);
1626 					break;
1627 			}
1628 		}
1629 	}
1630 }
1631 
1632 private auto extension(R)(R filename)
1633 	if (isForwardRange!R && isSomeChar!(ElementType!R))
1634 {
1635 	if (filename.empty) return filename;
1636 
1637 	static if (isArray!R) { // avoid auto decoding
1638 		filename = filename[1 .. $]; // ignore leading dot
1639 
1640 		R candidate;
1641 		while (filename.length) {
1642 			if (filename[0] == '.')
1643 				candidate = filename;
1644 			filename = filename[1 .. $];
1645 		}
1646 		return candidate;
1647 	} else {
1648 		filename.popFront(); // ignore leading dot
1649 
1650 		R candidate;
1651 		while (!filename.empty) {
1652 			if (filename.front == '.')
1653 				candidate = filename.save;
1654 			filename.popFront();
1655 		}
1656 		return candidate;
1657 	}
1658 }
1659 
1660 @safe nothrow unittest {
1661 	assert(extension("foo") == "");
1662 	assert(extension("foo.txt") == ".txt");
1663 	assert(extension(".foo") == "");
1664 	assert(extension(".foo.txt") == ".txt");
1665 	assert(extension("foo.bar.txt") == ".txt");
1666 }
1667 
1668 unittest {
1669 	assert(extension(InetPath("foo").head.name).equal(""));
1670 	assert(extension(InetPath("foo.txt").head.name).equal(".txt"));
1671 	assert(extension(InetPath(".foo").head.name).equal(""));
1672 	assert(extension(InetPath(".foo.txt").head.name).equal(".txt"));
1673 	assert(extension(InetPath("foo.bar.txt").head.name).equal(".txt"));
1674 }
1675 
1676 
1677 private auto stripExtension(R)(R filename)
1678 	if (isForwardRange!R && isSomeChar!(ElementType!R))
1679 {
1680 	static if (isArray!R) { // make sure to return a slice
1681 		if (!filename.length) return filename;
1682 		R r = filename;
1683 		r = r[1 .. $]; // ignore leading dot
1684 		size_t cnt = 0, rcnt = r.length;
1685 		while (r.length) {
1686 			if (r[0] == '.')
1687 				rcnt = cnt;
1688 			cnt++;
1689 			r = r[1 .. $];
1690 		}
1691 		return filename[0 .. rcnt + 1];
1692 	} else {
1693 		if (filename.empty) return filename.takeExactly(0);
1694 		R r = filename.save;
1695 		size_t cnt = 0, rcnt = size_t.max;
1696 		r.popFront(); // ignore leading dot
1697 		while (!r.empty) {
1698 			if (r.front == '.')
1699 				rcnt = cnt;
1700 			cnt++;
1701 			r.popFront();
1702 		}
1703 		if (rcnt == size_t.max) return filename.takeExactly(cnt + 1);
1704 		return filename.takeExactly(rcnt + 1);
1705 	}
1706 }
1707 
1708 @safe nothrow unittest {
1709 	assert(stripExtension("foo") == "foo");
1710 	assert(stripExtension("foo.txt") == "foo");
1711 	assert(stripExtension(".foo") == ".foo");
1712 	assert(stripExtension(".foo.txt") == ".foo");
1713 	assert(stripExtension("foo.bar.txt") == "foo.bar");
1714 }
1715 
1716 unittest { // test range based path
1717 	import std.utf : byWchar;
1718 
1719 	assert(stripExtension("foo".byWchar).equal("foo"));
1720 	assert(stripExtension("foo.txt".byWchar).equal("foo"));
1721 	assert(stripExtension(".foo".byWchar).equal(".foo"));
1722 	assert(stripExtension(".foo.txt".byWchar).equal(".foo"));
1723 	assert(stripExtension("foo.bar.txt".byWchar).equal("foo.bar"));
1724 
1725 	assert(stripExtension(InetPath("foo").head.name).equal("foo"));
1726 	assert(stripExtension(InetPath("foo.txt").head.name).equal("foo"));
1727 	assert(stripExtension(InetPath(".foo").head.name).equal(".foo"));
1728 	assert(stripExtension(InetPath(".foo.txt").head.name).equal(".foo"));
1729 	assert(stripExtension(InetPath("foo.bar.txt").head.name).equal("foo.bar"));
1730 }
1731 
1732 private static bool isAsciiAlphaNum(char ch)
1733 @safe nothrow pure @nogc {
1734 	return (uint(ch) & 0xDF) - 0x41 < 26 || uint(ch) - '0' <= 9;
1735 }
1736 
1737 unittest {
1738 	assert(!isAsciiAlphaNum('@'));
1739 	assert(isAsciiAlphaNum('A'));
1740 	assert(isAsciiAlphaNum('Z'));
1741 	assert(!isAsciiAlphaNum('['));
1742 	assert(!isAsciiAlphaNum('`'));
1743 	assert(isAsciiAlphaNum('a'));
1744 	assert(isAsciiAlphaNum('z'));
1745 	assert(!isAsciiAlphaNum('{'));
1746 	assert(!isAsciiAlphaNum('/'));
1747 	assert(isAsciiAlphaNum('0'));
1748 	assert(isAsciiAlphaNum('9'));
1749 	assert(!isAsciiAlphaNum(':'));
1750 }
1751 
1752 unittest { // regression tests
1753 	assert(NativePath("").bySegment.empty);
1754 }