diff --git a/codecov.yml b/codecov.yml index 4f00b32..f8b297d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,3 @@ ignore: - "source/tanya/async/event/iocp.d" - "source/tanya/async/iocp.d" - - "source/tanya/memory/types.d" diff --git a/source/tanya/memory/smartref.d b/source/tanya/memory/smartref.d index 4a00672..87aaa45 100644 --- a/source/tanya/memory/smartref.d +++ b/source/tanya/memory/smartref.d @@ -23,7 +23,7 @@ import std.range; import std.traits; import tanya.memory; -package template Payload(T) +private template Payload(T) { static if (is(T == class) || is(T == interface) || isArray!T) { @@ -35,7 +35,7 @@ package template Payload(T) } } -package final class RefCountedStore(T) +private final class RefCountedStore(T) { T payload; size_t counter = 1; @@ -68,14 +68,14 @@ package final class RefCountedStore(T) } } -package void separateDeleter(T)(RefCountedStore!T storage, +private void separateDeleter(T)(RefCountedStore!T storage, shared Allocator allocator) { allocator.dispose(storage.payload); allocator.dispose(storage); } -package void unifiedDeleter(T)(RefCountedStore!T storage, +private void unifiedDeleter(T)(RefCountedStore!T storage, shared Allocator allocator) { auto ptr1 = finalize(storage); diff --git a/source/tanya/memory/types.d b/source/tanya/memory/types.d deleted file mode 100644 index e255d5a..0000000 --- a/source/tanya/memory/types.d +++ /dev/null @@ -1,301 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Smart pointers. - * - * $(RED Deprecated. Use $(D_PSYMBOL tanya.memory.smartref) instead. - * This module will be removed in 0.8.0.) - * - * Copyright: Eugene Wissner 2016-2017. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - */ -deprecated("Use tanya.memory.smartref instead") -module tanya.memory.types; - -import core.exception; -import std.algorithm.comparison; -import std.algorithm.mutation; -import std.conv; -import std.range; -import std.traits; -import tanya.memory; -public import tanya.memory.smartref : RefCounted, Payload; - -version (unittest) -{ - private struct B - { - int prop; - @disable this(); - this(int param1) @nogc - { - prop = param1; - } - } -} - -/** - * $(D_PSYMBOL Scoped) stores an object that gets destroyed at the end of its scope. - * - * Params: - * T = Value type. - */ -struct Scoped(T) -{ - private Payload!T payload; - - invariant - { - assert(payload is null || allocator_ !is null); - } - - /** - * Takes ownership over $(D_PARAM value), setting the counter to 1. - * $(D_PARAM value) may be a pointer, an object or a dynamic array. - * - * Params: - * value = Value whose ownership is taken over. - * allocator = Allocator used to destroy the $(D_PARAM value) and to - * allocate/deallocate internal storage. - * - * Precondition: $(D_INLINECODE allocator !is null) - */ - this()(auto ref Payload!T value, - shared Allocator allocator = defaultAllocator) - { - this(allocator); - - move(value, this.payload); - static if (__traits(isRef, value)) - { - value = null; - } - } - - /// Ditto. - this(shared Allocator allocator) - in - { - assert(allocator !is null); - } - body - { - this.allocator_ = allocator; - } - - /** - * $(D_PSYMBOL Scoped) is noncopyable. - */ - @disable this(this); - - /** - * Destroys the owned object. - */ - ~this() - { - if (this.payload !is null) - { - allocator.dispose(this.payload); - } - } - - /** - * Initialized this $(D_PARAM Scoped) and takes ownership over - * $(D_PARAM rhs). - * - * To reset $(D_PSYMBOL Scoped) assign $(D_KEYWORD null). - * - * If the allocator wasn't set before, $(D_PSYMBOL defaultAllocator) will - * be used. If you need a different allocator, create a new - * $(D_PSYMBOL Scoped) and assign it. - * - * Params: - * rhs = New object. - * - * Returns: $(D_KEYWORD this). - */ - ref typeof(this) opAssign()(auto ref Payload!T rhs) - { - allocator.dispose(this.payload); - move(rhs, this.payload); - - return this; - } - - /// Ditto. - ref typeof(this) opAssign(typeof(null)) - { - allocator.dispose(this.payload); - return this; - } - - /// Ditto. - ref typeof(this) opAssign(typeof(this) rhs) - { - swap(this.allocator_, rhs.allocator_); - swap(this.payload, rhs.payload); - - return this; - } - - /** - * Returns: Reference to the owned object. - */ - Payload!T get() pure nothrow @safe @nogc - { - return payload; - } - - version (D_Ddoc) - { - /** - * Params: - * op = Operation. - * - * Dereferences the pointer. It is defined only for pointers, not for - * reference types like classes, that can be accessed directly. - * - * Returns: Reference to the pointed value. - */ - ref T opUnary(string op)() - if (op == "*"); - } - else static if (isPointer!(Payload!T)) - { - ref T opUnary(string op)() - if (op == "*") - { - return *payload; - } - } - - mixin DefaultAllocator; - alias get this; -} - -/// -@nogc unittest -{ - auto p = defaultAllocator.make!int(5); - auto s = Scoped!int(p, defaultAllocator); - assert(p is null); - assert(*s == 5); -} - -/// -@nogc unittest -{ - static bool destroyed = false; - - struct F - { - ~this() @nogc - { - destroyed = true; - } - } - { - auto s = Scoped!F(defaultAllocator.make!F(), defaultAllocator); - } - assert(destroyed); -} - -/** - * Constructs a new object of type $(D_PARAM T) and wraps it in a - * $(D_PSYMBOL Scoped) using $(D_PARAM args) as the parameter list for - * the constructor of $(D_PARAM T). - * - * Params: - * T = Type of the constructed object. - * A = Types of the arguments to the constructor of $(D_PARAM T). - * allocator = Allocator. - * args = Constructor arguments of $(D_PARAM T). - * - * Returns: Newly created $(D_PSYMBOL Scoped!T). - * - * Precondition: $(D_INLINECODE allocator !is null) - */ -Scoped!T scoped(T, A...)(shared Allocator allocator, auto ref A args) - if (!is(T == interface) && !isAbstractClass!T - && !isAssociativeArray!T && !isArray!T) -in -{ - assert(allocator !is null); -} -body -{ - auto payload = allocator.make!(T, shared Allocator, A)(args); - return Scoped!T(payload, allocator); -} - -/** - * Constructs a new array with $(D_PARAM size) elements and wraps it in a - * $(D_PSYMBOL Scoped). - * - * Params: - * T = Array type. - * size = Array size. - * allocator = Allocator. - * - * Returns: Newly created $(D_PSYMBOL Scoped!T). - * - * Precondition: $(D_INLINECODE allocator !is null - * && size <= size_t.max / ElementType!T.sizeof) - */ -Scoped!T scoped(T)(shared Allocator allocator, const size_t size) -@trusted - if (isArray!T) -in -{ - assert(allocator !is null); - assert(size <= size_t.max / ElementType!T.sizeof); -} -body -{ - auto payload = allocator.resize!(ElementType!T)(null, size); - return Scoped!T(payload, allocator); -} - -private unittest -{ - static assert(is(typeof(defaultAllocator.scoped!B(5)))); - static assert(is(typeof(defaultAllocator.scoped!(int[])(5)))); -} - -private unittest -{ - auto s = defaultAllocator.scoped!int(5); - assert(*s == 5); - - s = null; - assert(s is null); -} - -private unittest -{ - auto s = defaultAllocator.scoped!int(5); - assert(*s == 5); - - s = defaultAllocator.scoped!int(4); - assert(*s == 4); -} - -private @nogc unittest -{ - auto p1 = defaultAllocator.make!int(5); - auto p2 = p1; - auto rc = Scoped!int(p1, defaultAllocator); - - assert(p1 is null); - assert(rc.get() is p2); -} - -private @nogc unittest -{ - auto rc = Scoped!int(defaultAllocator); - assert(rc.allocator is defaultAllocator); -} diff --git a/source/tanya/net/uri.d b/source/tanya/net/uri.d index eac2f7a..da90011 100644 --- a/source/tanya/net/uri.d +++ b/source/tanya/net/uri.d @@ -13,7 +13,6 @@ module tanya.net.uri; import std.ascii : isAlphaNum, isDigit; -import std.traits : isSomeString; import std.uni : isAlpha, isNumber; import tanya.memory; @@ -151,9 +150,10 @@ struct URL goto ParsePath; } } - else // certain schemas like mailto: and zlib: may not have any / after them + else { - + // Schemas like mailto: and zlib: may not have any slash after + // them. if (!parsePort(value[pos .. $])) { this.scheme = value[0 .. pos]; @@ -384,7 +384,7 @@ struct URL assert(u.fragment == "fragment"); } -private unittest +private @nogc unittest { auto u = URL("127.0.0.1"); assert(u.path == "127.0.0.1"); @@ -540,17 +540,11 @@ private @nogc unittest * * Params: * T = "scheme", "host", "port", "user", "pass", "path", "query", - * "fragment" or $(D_KEYWORD null) for a struct with all components. + * "fragment". * source = The string containing the URL. * * Returns: Requested URL component. */ -URL parseURL(const char[] source) @nogc -{ - return URL(source); -} - -/// Ditto. auto parseURL(string T)(const char[] source) if (T == "scheme" || T == "host" @@ -565,6 +559,12 @@ if (T == "scheme" return mixin("ret." ~ T); } +/// Ditto. +URL parseURL(const char[] source) @nogc +{ + return URL(source); +} + /// @nogc unittest { diff --git a/source/tanya/network/inet.d b/source/tanya/network/inet.d deleted file mode 100644 index 964f58b..0000000 --- a/source/tanya/network/inet.d +++ /dev/null @@ -1,19 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * Internet utilities. - * - * $(RED Deprecated. Use $(D_PSYMBOL tanya.net.inet) instead. - * This module will be removed in 0.8.0.) - * - * Copyright: Eugene Wissner 2017. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - */ -deprecated("Use tanya.net.inet instead") -module tanya.network.inet; - -public import tanya.net.inet; diff --git a/source/tanya/network/package.d b/source/tanya/network/package.d index 14a6752..c81604f 100644 --- a/source/tanya/network/package.d +++ b/source/tanya/network/package.d @@ -12,6 +12,4 @@ */ module tanya.network; -public import tanya.network.inet; public import tanya.network.socket; -public import tanya.network.url; diff --git a/source/tanya/network/url.d b/source/tanya/network/url.d deleted file mode 100644 index cd78eea..0000000 --- a/source/tanya/network/url.d +++ /dev/null @@ -1,1192 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -/** - * URL parser. - * - * Copyright: Eugene Wissner 2016-2017. - * License: $(LINK2 https://www.mozilla.org/en-US/MPL/2.0/, - * Mozilla Public License, v. 2.0). - * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner) - */ -deprecated("Use tanya.net.uri instead") -module tanya.network.url; - -import std.ascii : isAlphaNum, isDigit; -import std.traits : isSomeString; -import std.uni : isAlpha, isNumber; -import std.uri; -import tanya.memory; - -version (unittest) private -{ - import std.typecons; - static Tuple!(string, string[string], ushort)[] URLTests; -} - -static this() -{ - version (unittest) - { - URLTests = [ - tuple(`127.0.0.1`, [ - "path": "127.0.0.1", - ], ushort(0)), - - tuple(`http://127.0.0.1`, [ - "scheme": "http", - "host": "127.0.0.1", - ], ushort(0)), - - tuple(`http://127.0.0.1/`, [ - "scheme": "http", - "host": "127.0.0.1", - "path": "/", - ], ushort(0)), - - tuple(`127.0.0.1/`, [ - "path": "127.0.0.1/", - ], ushort(0)), - - tuple(`127.0.0.1:60000/`, [ - "host": "127.0.0.1", - "path": "/", - ], ushort(60000)), - - tuple(`example.org`, [ - "path": "example.org", - ], ushort(0)), - - tuple(`example.org/`, [ - "path": "example.org/", - ], ushort(0)), - - tuple(`http://example.org`, [ - "scheme": "http", - "host": "example.org", - ], ushort(0)), - - tuple(`http://example.org/`, [ - "scheme": "http", - "host": "example.org", - "path": "/", - ], ushort(0)), - - tuple(`www.example.org`, [ - "path": "www.example.org", - ], ushort(0)), - - tuple(`www.example.org/`, [ - "path": "www.example.org/", - ], ushort(0)), - - tuple(`http://www.example.org`, [ - "scheme": "http", - "host": "www.example.org", - ], ushort(0)), - - tuple(`http://www.example.org/`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/", - ], ushort(0)), - - tuple(`www.example.org:2`, [ - "host": "www.example.org", - ], ushort(2)), - - tuple(`http://www.example.org:80`, [ - "scheme": "http", - "host": "www.example.org", - ], ushort(80)), - - tuple(`http://www.example.org:80/`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/", - ], ushort(80)), - - tuple(`http://www.example.org/index.html`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/index.html", - ], ushort(0)), - - tuple(`www.example.org/?`, [ - "path": "www.example.org/", - "query": "", - ], ushort(0)), - - tuple(`www.example.org:80/?`, [ - "host": "www.example.org", - "path": "/", - "query": "", - ], ushort(80)), - - tuple(`http://www.example.org/?`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/", - "query": "", - ], ushort(0)), - - tuple(`http://www.example.org:80/?`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/", - "query": "", - ], ushort(80)), - - tuple(`http://www.example.org:80/index.html`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/index.html", - ], ushort(80)), - - tuple(`http://www.example.org:80/foo/bar/index.html`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/foo/bar/index.html", - ], ushort(80)), - - tuple(`http://www.example.org:80/this/is/a/very/deep/directory/structure/and/file.png`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/this/is/a/very/deep/directory/structure/and/file.png", - ], ushort(80)), - - tuple(`http://www.example.org:80/deep/directory/structure/and/file.png?lots=1&of=2¶meters=3&too=4`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/deep/directory/structure/and/file.png", - "query": "lots=1&of=2¶meters=3&too=4", - ], ushort(80)), - - tuple(`http://www.example.org:80/this/is/a/very/deep/directory/structure/and/`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/this/is/a/very/deep/directory/structure/and/", - ], ushort(80)), - - tuple(`http://www.example.org:80/this/is/a/very/deep/directory/structure/and/file.php`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/this/is/a/very/deep/directory/structure/and/file.php", - ], ushort(80)), - - tuple(`http://www.example.org:80/this/../a/../deep/directory`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/this/../a/../deep/directory", - ], ushort(80)), - - tuple(`http://www.example.org:80/this/../a/../deep/directory/`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/this/../a/../deep/directory/", - ], ushort(80)), - - tuple(`http://www.example.org:80/this/is/a/very/deep/directory/../image.png`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/this/is/a/very/deep/directory/../image.png", - ], ushort(80)), - - tuple(`http://www.example.org:80/index.html`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/index.html", - ], ushort(80)), - - tuple(`http://www.example.org:80/index.html?`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/index.html", - "query": "", - ], ushort(80)), - - tuple(`http://www.example.org:80/#foo`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/", - "fragment": "foo", - ], ushort(80)), - - tuple(`http://www.example.org:80/?#`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/", - "query": "", - "fragment": "", - ], ushort(80)), - - tuple(`http://www.example.org:80/?test=1`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/", - "query": "test=1", - ], ushort(80)), - - tuple(`http://www.example.org/?test=1&`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/", - "query": "test=1&", - ], ushort(0)), - - tuple(`http://www.example.org:80/?&`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/", - "query": "&", - ], ushort(80)), - - tuple(`http://www.example.org:80/index.html?test=1&`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/index.html", - "query": "test=1&", - ], ushort(80)), - - tuple(`http://www.example.org/index.html?&`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/index.html", - "query": "&", - ], ushort(0)), - - tuple(`http://www.example.org:80/index.html?foo&`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/index.html", - "query": "foo&", - ], ushort(80)), - - tuple(`http://www.example.org/index.html?&foo`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/index.html", - "query": "&foo", - ], ushort(0)), - - tuple(`http://www.example.org:80/index.html?test=1&test2=char`, [ - "scheme": "http", - "host": "www.example.org", - "path": "/index.html", - "query": "test=1&test2=char", - ], ushort(80)), - - tuple(`www.example.org:80/index.html?test=1&test2=char#some_ref123`, [ - "host": "www.example.org", - "path": "/index.html", - "query": "test=1&test2=char", - "fragment": "some_ref123", - ], ushort(80)), - - tuple(`http://secret@www.example.org:80/index.html?test=1&test2=char#some_ref123`, [ - "scheme": "http", - "host": "www.example.org", - "user": "secret", - "path": "/index.html", - "query": "test=1&test2=char", - "fragment": "some_ref123", - ], ushort(80)), - - tuple(`http://secret:@www.example.org/index.html?test=1&test2=char#some_ref123`, [ - "scheme": "http", - "host": "www.example.org", - "user": "secret", - "pass": "", - "path": "/index.html", - "query": "test=1&test2=char", - "fragment": "some_ref123", - ], ushort(0)), - - tuple(`http://:hideout@www.example.org:80/index.html?test=1&test2=char#some_ref123`, [ - "scheme": "http", - "host": "www.example.org", - "user": "", - "pass": "hideout", - "path": "/index.html", - "query": "test=1&test2=char", - "fragment": "some_ref123", - ], ushort(80)), - - tuple(`http://secret:hideout@www.example.org/index.html?test=1&test2=char#some_ref123`, [ - "scheme": "http", - "host": "www.example.org", - "user": "secret", - "pass": "hideout", - "path": "/index.html", - "query": "test=1&test2=char", - "fragment": "some_ref123", - ], ushort(0)), - - tuple(`http://secret:hid:out@www.example.org:80/index.html?test=1&test2=int#some_ref123`, [ - "scheme": "http", - "host": "www.example.org", - "user": "secret", - "pass": "hid:out", - "path": "/index.html", - "query": "test=1&test2=int", - "fragment": "some_ref123", - ], ushort(80)), - - tuple(`nntp://news.example.org`, [ - "scheme": "nntp", - "host": "news.example.org", - ], ushort(0)), - - tuple(`ftp://ftp.gnu.org/gnu/glic/glibc.tar.gz`, [ - "scheme": "ftp", - "host": "ftp.gnu.org", - "path": "/gnu/glic/glibc.tar.gz", - ], ushort(0)), - - tuple(`zlib:http://foo@bar`, [ - "scheme": "zlib", - "path": "http://foo@bar", - ], ushort(0)), - - tuple(`zlib:filename.txt`, [ - "scheme": "zlib", - "path": "filename.txt", - ], ushort(0)), - - tuple(`zlib:/path/to/my/file/file.txt`, [ - "scheme": "zlib", - "path": "/path/to/my/file/file.txt", - ], ushort(0)), - - tuple(`foo://foo@bar`, [ - "scheme": "foo", - "host": "bar", - "user": "foo", - ], ushort(0)), - - tuple(`mailto:me@mydomain.com`, [ - "scheme": "mailto", - "path": "me@mydomain.com", - ], ushort(0)), - - tuple(`/foo.php?a=b&c=d`, [ - "path": "/foo.php", - "query": "a=b&c=d", - ], ushort(0)), - - tuple(`foo.php?a=b&c=d`, [ - "path": "foo.php", - "query": "a=b&c=d", - ], ushort(0)), - - tuple(`http://user:passwd@www.example.com:8080?bar=1&boom=0`, [ - "scheme": "http", - "host": "www.example.com", - "user": "user", - "pass": "passwd", - "query": "bar=1&boom=0", - ], ushort(8080)), - - tuple(`file:///path/to/file`, [ - "scheme": "file", - "path": "/path/to/file", - ], ushort(0)), - - tuple(`file://path/to/file`, [ - "scheme": "file", - "host": "path", - "path": "/to/file", - ], ushort(0)), - - tuple(`file:/path/to/file`, [ - "scheme": "file", - "path": "/path/to/file", - ], ushort(0)), - - tuple(`http://1.2.3.4:/abc.asp?a=1&b=2`, [ - "scheme": "http", - "host": "1.2.3.4", - "path": "/abc.asp", - "query": "a=1&b=2", - ], ushort(0)), - - tuple(`http://foo.com#bar`, [ - "scheme": "http", - "host": "foo.com", - "fragment": "bar", - ], ushort(0)), - - tuple(`scheme:`, [ - "scheme": "scheme", - ], ushort(0)), - - tuple(`foo+bar://baz@bang/bla`, [ - "scheme": "foo+bar", - "host": "bang", - "user": "baz", - "path": "/bla", - ], ushort(0)), - - tuple(`gg:9130731`, [ - "scheme": "gg", - "path": "9130731", - ], ushort(0)), - - tuple(`http://10.10.10.10/:80`, [ - "scheme": "http", - "host": "10.10.10.10", - "path": "/:80", - ], ushort(0)), - - tuple(`http://x:?`, [ - "scheme": "http", - "host": "x", - "query": "", - ], ushort(0)), - - tuple(`x:blah.com`, [ - "scheme": "x", - "path": "blah.com", - ], ushort(0)), - - tuple(`x:/blah.com`, [ - "scheme": "x", - "path": "/blah.com", - ], ushort(0)), - - tuple(`http://::?`, [ - "scheme": "http", - "host": ":", - "query": "", - ], ushort(0)), - - tuple(`http://::#`, [ - "scheme": "http", - "host": ":", - "fragment": "", - ], ushort(0)), - - tuple(`http://?:/`, [ - "scheme": "http", - "host": "?", - "path": "/", - ], ushort(0)), - - tuple(`http://@?:/`, [ - "scheme": "http", - "host": "?", - "user": "", - "path": "/", - ], ushort(0)), - - tuple(`file:///:`, [ - "scheme": "file", - "path": "/:", - ], ushort(0)), - - tuple(`file:///a:/`, [ - "scheme": "file", - "path": "a:/", - ], ushort(0)), - - tuple(`file:///ab:/`, [ - "scheme": "file", - "path": "/ab:/", - ], ushort(0)), - - tuple(`file:///a:/`, [ - "scheme": "file", - "path": "a:/", - ], ushort(0)), - - tuple(`file:///@:/`, [ - "scheme": "file", - "path": "@:/", - ], ushort(0)), - - tuple(`file:///:80/`, [ - "scheme": "file", - "path": "/:80/", - ], ushort(0)), - - tuple(`[]`, [ - "path": "[]", - ], ushort(0)), - - tuple(`http://[x:80]/`, [ - "scheme": "http", - "host": "[x:80]", - "path": "/", - ], ushort(0)), - - tuple(``, [ - "path": "", - ], ushort(0)), - - tuple(`/`, [ - "path": "/", - ], ushort(0)), - - tuple(`/rest/Users?filter={"id":"789"}`, [ - "path": "/rest/Users", - "query": `filter={"id":"789"}`, - ], ushort(0)), - - tuple(`//example.org`, [ - "host": "example.org", - ], ushort(0)), - - tuple(`/standard/?fq=B:20001`, [ - "path": "/standard/", - "query": "fq=B:20001", - ], ushort(0)), - - tuple(`/standard/?fq=B:200013`, [ - "path": "/standard/", - "query": "fq=B:200013", - ], ushort(0)), - - tuple(`/standard/?fq=home:012345`, [ - "path": "/standard/", - "query": "fq=home:012345", - ], ushort(0)), - - tuple(`/standard/?fq=home:01234`, [ - "path": "/standard/", - "query": "fq=home:01234", - ], ushort(0)), - - tuple(`http://user:pass@host`, [ - "scheme": "http", - "host": "host", - "user": "user", - "pass": "pass", - ], ushort(0)), - - tuple(`//user:pass@host`, [ - "host": "host", - "user": "user", - "pass": "pass", - ], ushort(0)), - - tuple(`//user@host`, [ - "host": "host", - "user": "user", - ], ushort(0)), - - tuple(`//example.org:99/hey?a=b#c=d`, [ - "host": "example.org", - "path": "/hey", - "query": "a=b", - "fragment": "c=d", - ], ushort(99)), - - tuple(`//example.org/hey?a=b#c=d`, [ - "host": "example.org", - "path": "/hey", - "query": "a=b", - "fragment": "c=d", - ], ushort(0)), - - tuple(`http://example.org/some/path.cgi?t=1#fragment?data`, [ - "scheme": "http", - "host": "example.org", - "path": "/some/path.cgi", - "query": "t=1", - "fragment": "fragment?data", - ], ushort(0)), - - tuple(`http://example.org/some/path.cgi#fragment?data`, [ - "scheme": "http", - "host": "example.org", - "path": "/some/path.cgi", - "fragment": "fragment?data", - ], ushort(0)), - - tuple(`x://::abc/?`, string[string].init, ushort(0)), - tuple(`http:///blah.com`, string[string].init, ushort(0)), - tuple(`http://:80`, string[string].init, ushort(0)), - tuple(`http://user@:80`, string[string].init, ushort(0)), - tuple(`http://user:pass@:80`, string[string].init, ushort(0)), - tuple(`http://:`, string[string].init, ushort(0)), - tuple(`http://@/`, string[string].init, ushort(0)), - tuple(`http://@:/`, string[string].init, ushort(0)), - tuple(`http://:/`, string[string].init, ushort(0)), - tuple(`http://?`, string[string].init, ushort(0)), - tuple(`http://#`, string[string].init, ushort(0)), - tuple(`http://:?`, string[string].init, ushort(0)), - tuple(`http://blah.com:123456`, string[string].init, ushort(0)), - tuple(`http://blah.com:70000`, string[string].init, ushort(0)), - tuple(`http://blah.com:abcdef`, string[string].init, ushort(0)), - tuple(`http://secret@hideout@www.example.org:80/index.html?test=1&test2=char#some_ref123`, - string[string].init, - ushort(0)), - tuple(`http://user:@pass@host/path?argument?value#etc`, string[string].init, ushort(0)), - tuple(`http://foo.com\@bar.com`, string[string].init, ushort(0)), - tuple(`http://email@address.com:pass@example.org`, string[string].init, ushort(0)), - tuple(`:`, string[string].init, ushort(0)), - ]; - } -} - -/** - * A Unique Resource Locator. - */ -struct URL -{ - /** The URL scheme. */ - const(char)[] scheme; - - /** The username. */ - const(char)[] user; - - /** The password. */ - const(char)[] pass; - - /** The hostname. */ - const(char)[] host; - - /** The port number. */ - ushort port; - - /** The path. */ - const(char)[] path; - - /** The query string. */ - const(char)[] query; - - /** The anchor. */ - const(char)[] fragment; - - /** - * Attempts to parse an URL from a string. - * Output string data (scheme, user, etc.) are just slices of input string (e.g., no memory allocation and copying). - * - * Params: - * source = The string containing the URL. - * - * Throws: $(D_PSYMBOL URIException) if the URL is malformed. - */ - this(in char[] source) - { - auto value = source; - ptrdiff_t pos = -1, endPos = value.length, start; - - foreach (i, ref c; source) - { - if (pos == -1 && c == ':') - { - pos = i; - } - if (endPos == value.length && (c == '?' || c == '#')) - { - endPos = i; - } - } - - // Check if the colon is a part of the scheme or the port and parse - // the appropriate part - if (value.length > 1 && value[0] == '/' && value[1] == '/') - { - // Relative scheme - start = 2; - } - else if (pos > 0) - { - // Validate scheme - // [ toLower(alpha) | digit | "+" | "-" | "." ] - foreach (ref c; value[0..pos]) - { - if (!c.isAlphaNum && c != '+' && c != '-' && c != '.') - { - if (endPos > pos) - { - if (!parsePort(value[pos..$])) - { - throw defaultAllocator.make!URIException("Failed to parse port"); - } - } - goto ParsePath; - } - } - - if (value.length == pos + 1) // only scheme is available - { - scheme = value[0 .. $ - 1]; - return; - } - else if (value.length > pos + 1 && value[pos + 1] == '/') - { - scheme = value[0..pos]; - - if (value.length > pos + 2 && value[pos + 2] == '/') - { - start = pos + 3; - if (scheme == "file" && value.length > start && value[start] == '/') - { - // Windows drive letters - if (value.length - start > 2 && value[start + 2] == ':') - { - ++start; - } - goto ParsePath; - } - } - else - { - start = pos + 1; - goto ParsePath; - } - } - else // certain schemas like mailto: and zlib: may not have any / after them - { - - if (!parsePort(value[pos..$])) - { - scheme = value[0..pos]; - start = pos + 1; - goto ParsePath; - } - } - } - else if (pos == 0 && parsePort(value[pos..$])) - { - // An URL shouldn't begin with a port number - throw defaultAllocator.make!URIException("URL begins with port"); - } - else - { - goto ParsePath; - } - - // Parse host - pos = -1; - for (ptrdiff_t i = start; i < value.length; ++i) - { - if (value[i] == '@') - { - pos = i; - } - else if (value[i] == '/') - { - endPos = i; - break; - } - } - - // Check for login and password - if (pos != -1) - { - // *( unreserved / pct-encoded / sub-delims / ":" ) - foreach (i, c; value[start..pos]) - { - if (c == ':') - { - if (user is null) - { - user = value[start .. start + i]; - pass = value[start + i + 1 .. pos]; - } - } - else if (!c.isAlpha && - !c.isNumber && - c != '!' && - c != ';' && - c != '=' && - c != '_' && - c != '~' && - !(c >= '$' && c <= '.')) - { - if (scheme !is null) - { - scheme = null; - } - if (user !is null) - { - user = null; - } - if (pass !is null) - { - pass = null; - } - throw make!URIException(defaultAllocator, - "Restricted characters in user information"); - } - } - if (user is null) - { - user = value[start..pos]; - } - - start = ++pos; - } - - pos = endPos; - if (endPos <= 1 || value[start] != '[' || value[endPos - 1] != ']') - { - // Short circuit portscan - // IPv6 embedded address - for (ptrdiff_t i = endPos - 1; i >= start; --i) - { - if (value[i] == ':') - { - pos = i; - if (port == 0 && !parsePort(value[i..endPos])) - { - if (scheme !is null) - { - scheme = null; - } - if (user !is null) - { - user = null; - } - if (pass !is null) - { - pass = null; - } - throw defaultAllocator.make!URIException("Invalid port"); - } - break; - } - } - } - - // Check if we have a valid host, if we don't reject the string as url - if (pos <= start) - { - if (scheme !is null) - { - scheme = null; - } - if (user !is null) - { - user = null; - } - if (pass !is null) - { - pass = null; - } - throw defaultAllocator.make!URIException("Invalid host"); - } - - host = value[start..pos]; - - if (endPos == value.length) - { - return; - } - - start = endPos; - - ParsePath: - endPos = value.length; - pos = -1; - foreach (i, ref c; value[start..$]) - { - if (c == '?' && pos == -1) - { - pos = start + i; - } - else if (c == '#') - { - endPos = start + i; - break; - } - } - if (pos == -1) - { - pos = endPos; - } - - if (pos > start) - { - path = value[start..pos]; - } - if (endPos >= ++pos) - { - query = value[pos..endPos]; - } - if (++endPos <= value.length) - { - fragment = value[endPos..$]; - } - } - - ~this() - { - if (scheme !is null) - { - scheme = null; - } - if (user !is null) - { - user = null; - } - if (pass !is null) - { - pass = null; - } - if (host !is null) - { - host = null; - } - if (path !is null) - { - path = null; - } - if (query !is null) - { - query = null; - } - if (fragment !is null) - { - fragment = null; - } - } - - /** - * Attempts to parse and set the port. - * - * Params: - * port = String beginning with a colon followed by the port number and - * an optional path (query string and/or fragment), like: - * `:12345/some_path` or `:12345`. - * - * Returns: Whether the port could be found. - */ - private bool parsePort(in char[] port) pure nothrow @safe @nogc - { - ptrdiff_t i = 1; - float lPort = 0; - - for (; i < port.length && port[i].isDigit() && i <= 6; ++i) - { - lPort += (port[i] - '0') / cast(float)(10 ^^ (i - 1)); - } - if (i == 1 && (i == port.length || port[i] == '/')) - { - return true; - } - else if (i == port.length || port[i] == '/') - { - lPort *= 10 ^^ (i - 2); - if (lPort > ushort.max) - { - return false; - } - this.port = cast(ushort)lPort; - return true; - } - return false; - } -} - -/// -unittest -{ - auto u = URL("example.org"); - assert(u.path == "example.org"); - - u = URL("relative/path"); - assert(u.path == "relative/path"); - - // Host and scheme - u = URL("https://example.org"); - assert(u.scheme == "https"); - assert(u.host == "example.org"); - assert(u.path is null); - assert(u.port == 0); - assert(u.fragment is null); - - // With user and port and path - u = URL("https://hilary:putnam@example.org:443/foo/bar"); - assert(u.scheme == "https"); - assert(u.host == "example.org"); - assert(u.path == "/foo/bar"); - assert(u.port == 443); - assert(u.user == "hilary"); - assert(u.pass == "putnam"); - assert(u.fragment is null); - - // With query string - u = URL("https://example.org/?login=true"); - assert(u.scheme == "https"); - assert(u.host == "example.org"); - assert(u.path == "/"); - assert(u.query == "login=true"); - assert(u.fragment is null); - - // With query string and fragment - u = URL("https://example.org/?login=false#label"); - assert(u.scheme == "https"); - assert(u.host == "example.org"); - assert(u.path == "/"); - assert(u.query == "login=false"); - assert(u.fragment == "label"); - - u = URL("redis://root:password@localhost:2201/path?query=value#fragment"); - assert(u.scheme == "redis"); - assert(u.user == "root"); - assert(u.pass == "password"); - assert(u.host == "localhost"); - assert(u.port == 2201); - assert(u.path == "/path"); - assert(u.query == "query=value"); - assert(u.fragment == "fragment"); -} - -private unittest -{ - foreach(t; URLTests) - { - if (t[1].length == 0 && t[2] == 0) - { - try - { - URL(t[0]); - assert(0); - } - catch (URIException e) - { - assert(1); - } - } - else - { - auto u = URL(t[0]); - assert("scheme" in t[1] ? u.scheme == t[1]["scheme"] : u.scheme is null, - t[0]); - assert("user" in t[1] ? u.user == t[1]["user"] : u.user is null, t[0]); - assert("pass" in t[1] ? u.pass == t[1]["pass"] : u.pass is null, t[0]); - assert("host" in t[1] ? u.host == t[1]["host"] : u.host is null, t[0]); - assert(u.port == t[2], t[0]); - assert("path" in t[1] ? u.path == t[1]["path"] : u.path is null, t[0]); - assert("query" in t[1] ? u.query == t[1]["query"] : u.query is null, t[0]); - if ("fragment" in t[1]) - { - assert(u.fragment == t[1]["fragment"], t[0]); - } - else - { - assert(u.fragment is null, t[0]); - } - } - } -} - -/** - * Contains possible URL components that can be returned from - * $(D_PSYMBOL parseURL). - */ -enum Component : string -{ - scheme = "scheme", - host = "host", - port = "port", - user = "user", - pass = "pass", - path = "path", - query = "query", - fragment = "fragment", -} - -/** - * Attempts to parse an URL from a string. - * - * Params: - * T = $(D_SYMBOL Component) member or $(D_KEYWORD null) for a - * struct with all components. - * source = The string containing the URL. - * - * Returns: Requested URL components. - */ -URL parseURL(typeof(null) T)(in char[] source) -{ - return URL(source); -} - -/// Ditto. -const(char)[] parseURL(immutable(char)[] T)(in char[] source) - if (T == "scheme" - || T == "host" - || T == "user" - || T == "pass" - || T == "path" - || T == "query" - || T == "fragment") -{ - auto ret = URL(source); - return mixin("ret." ~ T); -} - -/// Ditto. -ushort parseURL(immutable(char)[] T)(in char[] source) - if (T == "port") -{ - auto ret = URL(source); - return ret.port; -} - -unittest -{ - assert(parseURL!(Component.port)("http://example.org:5326") == 5326); -} - -private unittest -{ - foreach(t; URLTests) - { - if (t[1].length == 0 && t[2] == 0) - { - try - { - parseURL!(Component.port)(t[0]); - parseURL!(Component.user)(t[0]); - parseURL!(Component.pass)(t[0]); - parseURL!(Component.host)(t[0]); - parseURL!(Component.path)(t[0]); - parseURL!(Component.query)(t[0]); - parseURL!(Component.fragment)(t[0]); - assert(0); - } - catch (URIException e) - { - assert(1); - } - } - else - { - ushort port = parseURL!(Component.port)(t[0]); - auto component = parseURL!(Component.scheme)(t[0]); - assert("scheme" in t[1] ? component == t[1]["scheme"] : component is null, - t[0]); - component = parseURL!(Component.user)(t[0]); - assert("user" in t[1] ? component == t[1]["user"] : component is null, - t[0]); - component = parseURL!(Component.pass)(t[0]); - assert("pass" in t[1] ? component == t[1]["pass"] : component is null, - t[0]); - component = parseURL!(Component.host)(t[0]); - assert("host" in t[1] ? component == t[1]["host"] : component is null, - t[0]); - assert(port == t[2], t[0]); - component = parseURL!(Component.path)(t[0]); - assert("path" in t[1] ? component == t[1]["path"] : component is null, - t[0]); - component = parseURL!(Component.query)(t[0]); - assert("query" in t[1] ? component == t[1]["query"] : component is null, - t[0]); - component = parseURL!(Component.fragment)(t[0]); - if ("fragment" in t[1]) - { - assert(component == t[1]["fragment"], t[0]); - } - else - { - assert(component is null, t[0]); - } - } - } -}