summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/tanya/network/package.d16
-rw-r--r--source/tanya/network/socket.d2564
-rw-r--r--source/tanya/network/url.d1024
3 files changed, 1814 insertions, 1790 deletions
diff --git a/source/tanya/network/package.d b/source/tanya/network/package.d
new file mode 100644
index 0000000..96b987c
--- /dev/null
+++ b/source/tanya/network/package.d
@@ -0,0 +1,16 @@
+/* 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/. */
+
+/**
+ * Network programming.
+ *
+ * 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)
+ */
+module tanya.network;
+
+public import tanya.network.socket;
+public import tanya.network.url;
diff --git a/source/tanya/network/socket.d b/source/tanya/network/socket.d
index 17bcfa4..5dbbe41 100644
--- a/source/tanya/network/socket.d
+++ b/source/tanya/network/socket.d
@@ -3,7 +3,9 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
- * Copyright: Eugene Wissner 2016.
+ * Socket programming.
+ *
+ * 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)
@@ -22,532 +24,532 @@ import std.typecons;
version (Posix)
{
- import core.stdc.errno;
- import core.sys.posix.fcntl;
- import core.sys.posix.netdb;
- import core.sys.posix.netinet.in_;
- import core.sys.posix.sys.socket;
- import core.sys.posix.sys.time;
- import core.sys.posix.unistd;
-
- private enum SOCKET_ERROR = -1;
+ import core.stdc.errno;
+ import core.sys.posix.fcntl;
+ import core.sys.posix.netdb;
+ import core.sys.posix.netinet.in_;
+ import core.sys.posix.sys.socket;
+ import core.sys.posix.sys.time;
+ import core.sys.posix.unistd;
+
+ private enum SOCKET_ERROR = -1;
}
else version (Windows)
{
- import tanya.async.iocp;
- import core.sys.windows.basetyps;
- import core.sys.windows.mswsock;
- import core.sys.windows.winbase;
- import core.sys.windows.windef;
- import core.sys.windows.winsock2;
-
- enum : uint
- {
- IOC_UNIX = 0x00000000,
- IOC_WS2 = 0x08000000,
- IOC_PROTOCOL = 0x10000000,
- IOC_VOID = 0x20000000, /// No parameters.
- IOC_OUT = 0x40000000, /// Copy parameters back.
- IOC_IN = 0x80000000, /// Copy parameters into.
- IOC_VENDOR = 0x18000000,
- IOC_INOUT = (IOC_IN | IOC_OUT), /// Copy parameter into and get back.
- }
-
- template _WSAIO(int x, int y)
- {
- enum _WSAIO = IOC_VOID | x | y;
- }
- template _WSAIOR(int x, int y)
- {
- enum _WSAIOR = IOC_OUT | x | y;
- }
- template _WSAIOW(int x, int y)
- {
- enum _WSAIOW = IOC_IN | x | y;
- }
- template _WSAIORW(int x, int y)
- {
- enum _WSAIORW = IOC_INOUT | x | y;
- }
-
- alias SIO_ASSOCIATE_HANDLE = _WSAIOW!(IOC_WS2, 1);
- alias SIO_ENABLE_CIRCULAR_QUEUEING = _WSAIO!(IOC_WS2, 2);
- alias SIO_FIND_ROUTE = _WSAIOR!(IOC_WS2, 3);
- alias SIO_FLUSH = _WSAIO!(IOC_WS2, 4);
- alias SIO_GET_BROADCAST_ADDRESS = _WSAIOR!(IOC_WS2, 5);
- alias SIO_GET_EXTENSION_FUNCTION_POINTER = _WSAIORW!(IOC_WS2, 6);
- alias SIO_GET_QOS = _WSAIORW!(IOC_WS2, 7);
- alias SIO_GET_GROUP_QOS = _WSAIORW!(IOC_WS2, 8);
- alias SIO_MULTIPOINT_LOOPBACK = _WSAIOW!(IOC_WS2, 9);
- alias SIO_MULTICAST_SCOPE = _WSAIOW!(IOC_WS2, 10);
- alias SIO_SET_QOS = _WSAIOW!(IOC_WS2, 11);
- alias SIO_SET_GROUP_QOS = _WSAIOW!(IOC_WS2, 12);
- alias SIO_TRANSLATE_HANDLE = _WSAIORW!(IOC_WS2, 13);
- alias SIO_ROUTING_INTERFACE_QUERY = _WSAIORW!(IOC_WS2, 20);
- alias SIO_ROUTING_INTERFACE_CHANGE = _WSAIOW!(IOC_WS2, 21);
- alias SIO_ADDRESS_LIST_QUERY = _WSAIOR!(IOC_WS2, 22);
- alias SIO_ADDRESS_LIST_CHANGE = _WSAIO!(IOC_WS2, 23);
- alias SIO_QUERY_TARGET_PNP_HANDLE = _WSAIOR!(IOC_WS2, 24);
- alias SIO_NSP_NOTIFY_CHANGE = _WSAIOW!(IOC_WS2, 25);
-
- private alias GROUP = uint;
-
- enum
- {
- WSA_FLAG_OVERLAPPED = 0x01,
- MAX_PROTOCOL_CHAIN = 7,
- WSAPROTOCOL_LEN = 255,
- }
-
- struct WSAPROTOCOLCHAIN
- {
- int ChainLen;
- DWORD[MAX_PROTOCOL_CHAIN] ChainEntries;
- }
- alias LPWSAPROTOCOLCHAIN = WSAPROTOCOLCHAIN*;
-
- struct WSAPROTOCOL_INFO
- {
- DWORD dwServiceFlags1;
- DWORD dwServiceFlags2;
- DWORD dwServiceFlags3;
- DWORD dwServiceFlags4;
- DWORD dwProviderFlags;
- GUID ProviderId;
- DWORD dwCatalogEntryId;
- WSAPROTOCOLCHAIN ProtocolChain;
- int iVersion;
- int iAddressFamily;
- int iMaxSockAddr;
- int iMinSockAddr;
- int iSocketType;
- int iProtocol;
- int iProtocolMaxOffset;
- int iNetworkByteOrder;
- int iSecurityScheme;
- DWORD dwMessageSize;
- DWORD dwProviderReserved;
- TCHAR[WSAPROTOCOL_LEN + 1] szProtocol;
- }
- alias LPWSAPROTOCOL_INFO = WSAPROTOCOL_INFO*;
-
- extern (Windows) @nogc nothrow
- {
- private SOCKET WSASocketW(int af,
- int type,
- int protocol,
- LPWSAPROTOCOL_INFO lpProtocolInfo,
- GROUP g,
- DWORD dwFlags);
- int WSARecv(SOCKET s,
- LPWSABUF lpBuffers,
- DWORD dwBufferCount,
- LPDWORD lpNumberOfBytesRecvd,
- LPDWORD lpFlags,
- LPOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
- int WSASend(SOCKET s,
- LPWSABUF lpBuffers,
- DWORD dwBufferCount,
- LPDWORD lpNumberOfBytesRecvd,
- DWORD lpFlags,
- LPOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
- int WSAIoctl(SOCKET s,
- uint dwIoControlCode,
- void* lpvInBuffer,
- uint cbInBuffer,
- void* lpvOutBuffer,
- uint cbOutBuffer,
- uint* lpcbBytesReturned,
- LPWSAOVERLAPPED lpOverlapped,
- LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
- alias LPFN_ACCEPTEX = BOOL function(SOCKET,
- SOCKET,
- PVOID,
- DWORD,
- DWORD,
- DWORD,
- LPDWORD,
- LPOVERLAPPED);
- }
- alias WSASocket = WSASocketW;
-
- alias LPFN_GETACCEPTEXSOCKADDRS = VOID function(PVOID,
- DWORD,
- DWORD,
- DWORD,
- SOCKADDR**,
- LPINT,
- SOCKADDR**,
- LPINT);
- const GUID WSAID_GETACCEPTEXSOCKADDRS = {0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]};
-
- struct WSABUF {
- ULONG len;
- CHAR* buf;
- }
- alias WSABUF* LPWSABUF;
-
- struct WSAOVERLAPPED {
- ULONG_PTR Internal;
- ULONG_PTR InternalHigh;
- union {
- struct {
- DWORD Offset;
- DWORD OffsetHigh;
- }
- PVOID Pointer;
- }
- HANDLE hEvent;
- }
- alias LPWSAOVERLAPPED = WSAOVERLAPPED*;
-
- enum SO_UPDATE_ACCEPT_CONTEXT = 0x700B;
-
- enum OverlappedSocketEvent
- {
- accept = 1,
- read = 2,
- write = 3,
- }
-
- class SocketState : State
- {
- private WSABUF buffer;
- }
-
- /**
- * Socket returned if a connection has been established.
- */
- class OverlappedConnectedSocket : ConnectedSocket
- {
- /**
- * Create a socket.
- *
- * Params:
- * handle = Socket handle.
- * af = Address family.
- */
- this(socket_t handle, AddressFamily af) @nogc
- {
- super(handle, af);
- }
-
- /**
- * Begins to asynchronously receive data from a connected socket.
- *
- * Params:
- * buffer = Storage location for the received data.
- * flags = Flags.
- * overlapped = Unique operation identifier.
- *
- * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
- * $(D_KEYWORD false) otherwise.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to receive.
- */
- bool beginReceive(ubyte[] buffer,
- SocketState overlapped,
- Flags flags = Flags(Flag.none)) @nogc @trusted
- {
- auto receiveFlags = cast(DWORD) flags;
-
- overlapped.handle = cast(HANDLE) handle_;
- overlapped.event = OverlappedSocketEvent.read;
- overlapped.buffer.len = cast(ULONG) buffer.length;
- overlapped.buffer.buf = cast(char*) buffer.ptr;
-
- auto result = WSARecv(handle_,
- &overlapped.buffer,
- 1u,
- NULL,
- &receiveFlags,
- &overlapped.overlapped,
- NULL);
-
- if (result == SOCKET_ERROR && !wouldHaveBlocked)
- {
- throw defaultAllocator.make!SocketException("Unable to receive");
- }
- return result == 0;
- }
-
- /**
- * Ends a pending asynchronous read.
- *
- * Params
- * overlapped = Unique operation identifier.
- *
- * Returns: Number of bytes received.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to receive.
- */
- int endReceive(SocketState overlapped) @nogc @trusted
- out (count)
- {
- assert(count >= 0);
- }
- body
- {
- DWORD lpNumber;
- BOOL result = GetOverlappedResult(overlapped.handle,
- &overlapped.overlapped,
- &lpNumber,
- FALSE);
- if (result == FALSE && !wouldHaveBlocked)
- {
- disconnected_ = true;
- throw defaultAllocator.make!SocketException("Unable to receive");
- }
- if (lpNumber == 0)
- {
- disconnected_ = true;
- }
- return lpNumber;
- }
-
- /**
- * Sends data asynchronously to a connected socket.
- *
- * Params:
- * buffer = Data to be sent.
- * flags = Flags.
- * overlapped = Unique operation identifier.
- *
- * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
- * $(D_KEYWORD false) otherwise.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to send.
- */
- bool beginSend(ubyte[] buffer,
- SocketState overlapped,
- Flags flags = Flags(Flag.none)) @nogc @trusted
- {
- overlapped.handle = cast(HANDLE) handle_;
- overlapped.event = OverlappedSocketEvent.write;
- overlapped.buffer.len = cast(ULONG) buffer.length;
- overlapped.buffer.buf = cast(char*) buffer.ptr;
-
- auto result = WSASend(handle_,
- &overlapped.buffer,
- 1u,
- NULL,
- cast(DWORD) flags,
- &overlapped.overlapped,
- NULL);
-
- if (result == SOCKET_ERROR && !wouldHaveBlocked)
- {
- disconnected_ = true;
- throw defaultAllocator.make!SocketException("Unable to send");
- }
- return result == 0;
- }
-
- /**
- * Ends a pending asynchronous send.
- *
- * Params
- * overlapped = Unique operation identifier.
- *
- * Returns: Number of bytes sent.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to receive.
- */
- int endSend(SocketState overlapped) @nogc @trusted
- out (count)
- {
- assert(count >= 0);
- }
- body
- {
- DWORD lpNumber;
- BOOL result = GetOverlappedResult(overlapped.handle,
- &overlapped.overlapped,
- &lpNumber,
- FALSE);
- if (result == FALSE && !wouldHaveBlocked)
- {
- disconnected_ = true;
- throw defaultAllocator.make!SocketException("Unable to receive");
- }
- return lpNumber;
- }
- }
-
- class OverlappedStreamSocket : StreamSocket
- {
- /// Accept extension function pointer.
- package LPFN_ACCEPTEX acceptExtension;
-
- /**
- * Create a socket.
- *
- * Params:
- * af = Address family.
- *
- * Throws: $(D_PSYMBOL SocketException) on errors.
- */
- this(AddressFamily af) @nogc @trusted
- {
- super(af);
- scope (failure)
- {
- this.close();
- }
- blocking = false;
-
- GUID guidAcceptEx = WSAID_ACCEPTEX;
- DWORD dwBytes;
-
- auto result = WSAIoctl(handle_,
- SIO_GET_EXTENSION_FUNCTION_POINTER,
- &guidAcceptEx,
- guidAcceptEx.sizeof,
- &acceptExtension,
- acceptExtension.sizeof,
- &dwBytes,
- NULL,
- NULL);
- if (!result == SOCKET_ERROR)
- {
- throw make!SocketException(defaultAllocator,
- "Unable to retrieve an accept extension function pointer");
- }
- }
-
- /**
- * Begins an asynchronous operation to accept an incoming connection attempt.
- *
- * Params:
- * overlapped = Unique operation identifier.
- *
- * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
- * $(D_KEYWORD false) otherwise.
- *
- * Throws: $(D_PSYMBOL SocketException) on accept errors.
- */
- bool beginAccept(SocketState overlapped) @nogc @trusted
- {
- auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);
- if (socket == socket_t.init)
- {
- throw defaultAllocator.make!SocketException("Unable to create socket");
- }
- scope (failure)
- {
- closesocket(socket);
- }
- DWORD dwBytes;
- overlapped.handle = cast(HANDLE) socket;
- overlapped.event = OverlappedSocketEvent.accept;
-
- immutable len = (sockaddr_in.sizeof + 16) * 2;
- overlapped.buffer.len = len;
- overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
-
- // We don't want to get any data now, but only start to accept the connections
- BOOL result = acceptExtension(handle_,
- socket,
- overlapped.buffer.buf,
- 0u,
- sockaddr_in.sizeof + 16,
- sockaddr_in.sizeof + 16,
- &dwBytes,
- &overlapped.overlapped);
- if (result == FALSE && !wouldHaveBlocked)
- {
- throw defaultAllocator.make!SocketException("Unable to accept socket connection");
- }
- return result == TRUE;
- }
-
- /**
- * Asynchronously accepts an incoming connection attempt and creates a
- * new socket to handle remote host communication.
- *
- * Params:
- * overlapped = Unique operation identifier.
- *
- * Returns: Connected socket.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to accept.
- */
- OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted
- {
- scope (exit)
- {
- defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
- }
- auto socket = make!OverlappedConnectedSocket(defaultAllocator,
- cast(socket_t) overlapped.handle,
- addressFamily);
- scope (failure)
- {
- defaultAllocator.dispose(socket);
- }
- socket.setOption(SocketOptionLevel.SOCKET,
- cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
- cast(size_t) handle);
- return socket;
- }
- }
+ import tanya.async.iocp;
+ import core.sys.windows.basetyps;
+ import core.sys.windows.mswsock;
+ import core.sys.windows.winbase;
+ import core.sys.windows.windef;
+ import core.sys.windows.winsock2;
+
+ enum : uint
+ {
+ IOC_UNIX = 0x00000000,
+ IOC_WS2 = 0x08000000,
+ IOC_PROTOCOL = 0x10000000,
+ IOC_VOID = 0x20000000, /// No parameters.
+ IOC_OUT = 0x40000000, /// Copy parameters back.
+ IOC_IN = 0x80000000, /// Copy parameters into.
+ IOC_VENDOR = 0x18000000,
+ IOC_INOUT = (IOC_IN | IOC_OUT), /// Copy parameter into and get back.
+ }
+
+ template _WSAIO(int x, int y)
+ {
+ enum _WSAIO = IOC_VOID | x | y;
+ }
+ template _WSAIOR(int x, int y)
+ {
+ enum _WSAIOR = IOC_OUT | x | y;
+ }
+ template _WSAIOW(int x, int y)
+ {
+ enum _WSAIOW = IOC_IN | x | y;
+ }
+ template _WSAIORW(int x, int y)
+ {
+ enum _WSAIORW = IOC_INOUT | x | y;
+ }
+
+ alias SIO_ASSOCIATE_HANDLE = _WSAIOW!(IOC_WS2, 1);
+ alias SIO_ENABLE_CIRCULAR_QUEUEING = _WSAIO!(IOC_WS2, 2);
+ alias SIO_FIND_ROUTE = _WSAIOR!(IOC_WS2, 3);
+ alias SIO_FLUSH = _WSAIO!(IOC_WS2, 4);
+ alias SIO_GET_BROADCAST_ADDRESS = _WSAIOR!(IOC_WS2, 5);
+ alias SIO_GET_EXTENSION_FUNCTION_POINTER = _WSAIORW!(IOC_WS2, 6);
+ alias SIO_GET_QOS = _WSAIORW!(IOC_WS2, 7);
+ alias SIO_GET_GROUP_QOS = _WSAIORW!(IOC_WS2, 8);
+ alias SIO_MULTIPOINT_LOOPBACK = _WSAIOW!(IOC_WS2, 9);
+ alias SIO_MULTICAST_SCOPE = _WSAIOW!(IOC_WS2, 10);
+ alias SIO_SET_QOS = _WSAIOW!(IOC_WS2, 11);
+ alias SIO_SET_GROUP_QOS = _WSAIOW!(IOC_WS2, 12);
+ alias SIO_TRANSLATE_HANDLE = _WSAIORW!(IOC_WS2, 13);
+ alias SIO_ROUTING_INTERFACE_QUERY = _WSAIORW!(IOC_WS2, 20);
+ alias SIO_ROUTING_INTERFACE_CHANGE = _WSAIOW!(IOC_WS2, 21);
+ alias SIO_ADDRESS_LIST_QUERY = _WSAIOR!(IOC_WS2, 22);
+ alias SIO_ADDRESS_LIST_CHANGE = _WSAIO!(IOC_WS2, 23);
+ alias SIO_QUERY_TARGET_PNP_HANDLE = _WSAIOR!(IOC_WS2, 24);
+ alias SIO_NSP_NOTIFY_CHANGE = _WSAIOW!(IOC_WS2, 25);
+
+ private alias GROUP = uint;
+
+ enum
+ {
+ WSA_FLAG_OVERLAPPED = 0x01,
+ MAX_PROTOCOL_CHAIN = 7,
+ WSAPROTOCOL_LEN = 255,
+ }
+
+ struct WSAPROTOCOLCHAIN
+ {
+ int ChainLen;
+ DWORD[MAX_PROTOCOL_CHAIN] ChainEntries;
+ }
+ alias LPWSAPROTOCOLCHAIN = WSAPROTOCOLCHAIN*;
+
+ struct WSAPROTOCOL_INFO
+ {
+ DWORD dwServiceFlags1;
+ DWORD dwServiceFlags2;
+ DWORD dwServiceFlags3;
+ DWORD dwServiceFlags4;
+ DWORD dwProviderFlags;
+ GUID ProviderId;
+ DWORD dwCatalogEntryId;
+ WSAPROTOCOLCHAIN ProtocolChain;
+ int iVersion;
+ int iAddressFamily;
+ int iMaxSockAddr;
+ int iMinSockAddr;
+ int iSocketType;
+ int iProtocol;
+ int iProtocolMaxOffset;
+ int iNetworkByteOrder;
+ int iSecurityScheme;
+ DWORD dwMessageSize;
+ DWORD dwProviderReserved;
+ TCHAR[WSAPROTOCOL_LEN + 1] szProtocol;
+ }
+ alias LPWSAPROTOCOL_INFO = WSAPROTOCOL_INFO*;
+
+ extern (Windows) @nogc nothrow
+ {
+ private SOCKET WSASocketW(int af,
+ int type,
+ int protocol,
+ LPWSAPROTOCOL_INFO lpProtocolInfo,
+ GROUP g,
+ DWORD dwFlags);
+ int WSARecv(SOCKET s,
+ LPWSABUF lpBuffers,
+ DWORD dwBufferCount,
+ LPDWORD lpNumberOfBytesRecvd,
+ LPDWORD lpFlags,
+ LPOVERLAPPED lpOverlapped,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+ int WSASend(SOCKET s,
+ LPWSABUF lpBuffers,
+ DWORD dwBufferCount,
+ LPDWORD lpNumberOfBytesRecvd,
+ DWORD lpFlags,
+ LPOVERLAPPED lpOverlapped,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+ int WSAIoctl(SOCKET s,
+ uint dwIoControlCode,
+ void* lpvInBuffer,
+ uint cbInBuffer,
+ void* lpvOutBuffer,
+ uint cbOutBuffer,
+ uint* lpcbBytesReturned,
+ LPWSAOVERLAPPED lpOverlapped,
+ LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
+ alias LPFN_ACCEPTEX = BOOL function(SOCKET,
+ SOCKET,
+ PVOID,
+ DWORD,
+ DWORD,
+ DWORD,
+ LPDWORD,
+ LPOVERLAPPED);
+ }
+ alias WSASocket = WSASocketW;
+
+ alias LPFN_GETACCEPTEXSOCKADDRS = VOID function(PVOID,
+ DWORD,
+ DWORD,
+ DWORD,
+ SOCKADDR**,
+ LPINT,
+ SOCKADDR**,
+ LPINT);
+ const GUID WSAID_GETACCEPTEXSOCKADDRS = {0xb5367df2,0xcbac,0x11cf,[0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92]};
+
+ struct WSABUF {
+ ULONG len;
+ CHAR* buf;
+ }
+ alias WSABUF* LPWSABUF;
+
+ struct WSAOVERLAPPED {
+ ULONG_PTR Internal;
+ ULONG_PTR InternalHigh;
+ union {
+ struct {
+ DWORD Offset;
+ DWORD OffsetHigh;
+ }
+ PVOID Pointer;
+ }
+ HANDLE hEvent;
+ }
+ alias LPWSAOVERLAPPED = WSAOVERLAPPED*;
+
+ enum SO_UPDATE_ACCEPT_CONTEXT = 0x700B;
+
+ enum OverlappedSocketEvent
+ {
+ accept = 1,
+ read = 2,
+ write = 3,
+ }
+
+ class SocketState : State
+ {
+ private WSABUF buffer;
+ }
+
+ /**
+ * Socket returned if a connection has been established.
+ */
+ class OverlappedConnectedSocket : ConnectedSocket
+ {
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * handle = Socket handle.
+ * af = Address family.
+ */
+ this(socket_t handle, AddressFamily af) @nogc
+ {
+ super(handle, af);
+ }
+
+ /**
+ * Begins to asynchronously receive data from a connected socket.
+ *
+ * Params:
+ * buffer = Storage location for the received data.
+ * flags = Flags.
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
+ * $(D_KEYWORD false) otherwise.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to receive.
+ */
+ bool beginReceive(ubyte[] buffer,
+ SocketState overlapped,
+ Flags flags = Flags(Flag.none)) @nogc @trusted
+ {
+ auto receiveFlags = cast(DWORD) flags;
+
+ overlapped.handle = cast(HANDLE) handle_;
+ overlapped.event = OverlappedSocketEvent.read;
+ overlapped.buffer.len = cast(ULONG) buffer.length;
+ overlapped.buffer.buf = cast(char*) buffer.ptr;
+
+ auto result = WSARecv(handle_,
+ &overlapped.buffer,
+ 1u,
+ NULL,
+ &receiveFlags,
+ &overlapped.overlapped,
+ NULL);
+
+ if (result == SOCKET_ERROR && !wouldHaveBlocked)
+ {
+ throw defaultAllocator.make!SocketException("Unable to receive");
+ }
+ return result == 0;
+ }
+
+ /**
+ * Ends a pending asynchronous read.
+ *
+ * Params
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: Number of bytes received.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to receive.
+ */
+ int endReceive(SocketState overlapped) @nogc @trusted
+ out (count)
+ {
+ assert(count >= 0);
+ }
+ body
+ {
+ DWORD lpNumber;
+ BOOL result = GetOverlappedResult(overlapped.handle,
+ &overlapped.overlapped,
+ &lpNumber,
+ FALSE);
+ if (result == FALSE && !wouldHaveBlocked)
+ {
+ disconnected_ = true;
+ throw defaultAllocator.make!SocketException("Unable to receive");
+ }
+ if (lpNumber == 0)
+ {
+ disconnected_ = true;
+ }
+ return lpNumber;
+ }
+
+ /**
+ * Sends data asynchronously to a connected socket.
+ *
+ * Params:
+ * buffer = Data to be sent.
+ * flags = Flags.
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
+ * $(D_KEYWORD false) otherwise.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to send.
+ */
+ bool beginSend(ubyte[] buffer,
+ SocketState overlapped,
+ Flags flags = Flags(Flag.none)) @nogc @trusted
+ {
+ overlapped.handle = cast(HANDLE) handle_;
+ overlapped.event = OverlappedSocketEvent.write;
+ overlapped.buffer.len = cast(ULONG) buffer.length;
+ overlapped.buffer.buf = cast(char*) buffer.ptr;
+
+ auto result = WSASend(handle_,
+ &overlapped.buffer,
+ 1u,
+ NULL,
+ cast(DWORD) flags,
+ &overlapped.overlapped,
+ NULL);
+
+ if (result == SOCKET_ERROR && !wouldHaveBlocked)
+ {
+ disconnected_ = true;
+ throw defaultAllocator.make!SocketException("Unable to send");
+ }
+ return result == 0;
+ }
+
+ /**
+ * Ends a pending asynchronous send.
+ *
+ * Params
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: Number of bytes sent.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to receive.
+ */
+ int endSend(SocketState overlapped) @nogc @trusted
+ out (count)
+ {
+ assert(count >= 0);
+ }
+ body
+ {
+ DWORD lpNumber;
+ BOOL result = GetOverlappedResult(overlapped.handle,
+ &overlapped.overlapped,
+ &lpNumber,
+ FALSE);
+ if (result == FALSE && !wouldHaveBlocked)
+ {
+ disconnected_ = true;
+ throw defaultAllocator.make!SocketException("Unable to receive");
+ }
+ return lpNumber;
+ }
+ }
+
+ class OverlappedStreamSocket : StreamSocket
+ {
+ /// Accept extension function pointer.
+ package LPFN_ACCEPTEX acceptExtension;
+
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * af = Address family.
+ *
+ * Throws: $(D_PSYMBOL SocketException) on errors.
+ */
+ this(AddressFamily af) @nogc @trusted
+ {
+ super(af);
+ scope (failure)
+ {
+ this.close();
+ }
+ blocking = false;
+
+ GUID guidAcceptEx = WSAID_ACCEPTEX;
+ DWORD dwBytes;
+
+ auto result = WSAIoctl(handle_,
+ SIO_GET_EXTENSION_FUNCTION_POINTER,
+ &guidAcceptEx,
+ guidAcceptEx.sizeof,
+ &acceptExtension,
+ acceptExtension.sizeof,
+ &dwBytes,
+ NULL,
+ NULL);
+ if (!result == SOCKET_ERROR)
+ {
+ throw make!SocketException(defaultAllocator,
+ "Unable to retrieve an accept extension function pointer");
+ }
+ }
+
+ /**
+ * Begins an asynchronous operation to accept an incoming connection attempt.
+ *
+ * Params:
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: $(D_KEYWORD true) if the operation could be finished synchronously.
+ * $(D_KEYWORD false) otherwise.
+ *
+ * Throws: $(D_PSYMBOL SocketException) on accept errors.
+ */
+ bool beginAccept(SocketState overlapped) @nogc @trusted
+ {
+ auto socket = cast(socket_t) socket(addressFamily, SOCK_STREAM, 0);
+ if (socket == socket_t.init)
+ {
+ throw defaultAllocator.make!SocketException("Unable to create socket");
+ }
+ scope (failure)
+ {
+ closesocket(socket);
+ }
+ DWORD dwBytes;
+ overlapped.handle = cast(HANDLE) socket;
+ overlapped.event = OverlappedSocketEvent.accept;
+
+ immutable len = (sockaddr_in.sizeof + 16) * 2;
+ overlapped.buffer.len = len;
+ overlapped.buffer.buf = cast(char*) defaultAllocator.allocate(len).ptr;
+
+ // We don't want to get any data now, but only start to accept the connections
+ BOOL result = acceptExtension(handle_,
+ socket,
+ overlapped.buffer.buf,
+ 0u,
+ sockaddr_in.sizeof + 16,
+ sockaddr_in.sizeof + 16,
+ &dwBytes,
+ &overlapped.overlapped);
+ if (result == FALSE && !wouldHaveBlocked)
+ {
+ throw defaultAllocator.make!SocketException("Unable to accept socket connection");
+ }
+ return result == TRUE;
+ }
+
+ /**
+ * Asynchronously accepts an incoming connection attempt and creates a
+ * new socket to handle remote host communication.
+ *
+ * Params:
+ * overlapped = Unique operation identifier.
+ *
+ * Returns: Connected socket.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to accept.
+ */
+ OverlappedConnectedSocket endAccept(SocketState overlapped) @nogc @trusted
+ {
+ scope (exit)
+ {
+ defaultAllocator.dispose(overlapped.buffer.buf[0 .. overlapped.buffer.len]);
+ }
+ auto socket = make!OverlappedConnectedSocket(defaultAllocator,
+ cast(socket_t) overlapped.handle,
+ addressFamily);
+ scope (failure)
+ {
+ defaultAllocator.dispose(socket);
+ }
+ socket.setOption(SocketOptionLevel.SOCKET,
+ cast(SocketOption) SO_UPDATE_ACCEPT_CONTEXT,
+ cast(size_t) handle);
+ return socket;
+ }
+ }
}
version (linux)
{
- enum SOCK_NONBLOCK = O_NONBLOCK;
- extern(C) int accept4(int, sockaddr*, socklen_t*, int flags) @nogc nothrow;
+ enum SOCK_NONBLOCK = O_NONBLOCK;
+ extern(C) int accept4(int, sockaddr*, socklen_t*, int flags) @nogc nothrow;
}
else version (OSX)
{
- version = MacBSD;
+ version = MacBSD;
}
else version (iOS)
{
- version = MacBSD;
+ version = MacBSD;
}
else version (FreeBSD)
{
- version = MacBSD;
+ version = MacBSD;
}
else version (OpenBSD)
{
- version = MacBSD;
+ version = MacBSD;
}
else version (DragonFlyBSD)
{
- version = MacBSD;
+ version = MacBSD;
}
version (MacBSD)
{
- enum ESOCKTNOSUPPORT = 44; /// Socket type not suppoted.
+ enum ESOCKTNOSUPPORT = 44; /// Socket type not suppoted.
}
private immutable
{
- typeof(&getaddrinfo) getaddrinfoPointer;
- typeof(&freeaddrinfo) freeaddrinfoPointer;
+ typeof(&getaddrinfo) getaddrinfoPointer;
+ typeof(&freeaddrinfo) freeaddrinfoPointer;
}
shared static this()
{
- version (Windows)
- {
- auto ws2Lib = GetModuleHandle("ws2_32.dll");
-
- getaddrinfoPointer = cast(typeof(getaddrinfoPointer))
- GetProcAddress(ws2Lib, "getaddrinfo");
- freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer))
- GetProcAddress(ws2Lib, "freeaddrinfo");
- }
- else version (Posix)
- {
- getaddrinfoPointer = &getaddrinfo;
- freeaddrinfoPointer = &freeaddrinfo;
- }
+ version (Windows)
+ {
+ auto ws2Lib = GetModuleHandle("ws2_32.dll");
+
+ getaddrinfoPointer = cast(typeof(getaddrinfoPointer))
+ GetProcAddress(ws2Lib, "getaddrinfo");
+ freeaddrinfoPointer = cast(typeof(freeaddrinfoPointer))
+ GetProcAddress(ws2Lib, "freeaddrinfo");
+ }
+ else version (Posix)
+ {
+ getaddrinfoPointer = &getaddrinfo;
+ freeaddrinfoPointer = &freeaddrinfo;
+ }
}
/**
@@ -555,32 +557,32 @@ shared static this()
*/
enum SocketError : int
{
- /// Unknown error
- unknown = 0,
- /// Firewall rules forbid connection.
- accessDenied = EPERM,
- /// A socket operation was attempted on a non-socket.
- notSocket = EBADF,
- /// The network is not available.
- networkDown = ECONNABORTED,
- /// An invalid pointer address was detected by the underlying socket provider.
- fault = EFAULT,
- /// An invalid argument was supplied to a $(D_PSYMBOL Socket) member.
- invalidArgument = EINVAL,
- /// The limit on the number of open sockets has been reached.
- tooManyOpenSockets = ENFILE,
- /// No free buffer space is available for a Socket operation.
- noBufferSpaceAvailable = ENOBUFS,
- /// The address family is not supported by the protocol family.
- operationNotSupported = EOPNOTSUPP,
- /// The protocol is not implemented or has not been configured.
- protocolNotSupported = EPROTONOSUPPORT,
- /// Protocol error.
- protocolError = EPROTOTYPE,
- /// The connection attempt timed out, or the connected host has failed to respond.
- timedOut = ETIMEDOUT,
- /// The support for the specified socket type does not exist in this address family.
- socketNotSupported = ESOCKTNOSUPPORT,
+ /// Unknown error
+ unknown = 0,
+ /// Firewall rules forbid connection.
+ accessDenied = EPERM,
+ /// A socket operation was attempted on a non-socket.
+ notSocket = EBADF,
+ /// The network is not available.
+ networkDown = ECONNABORTED,
+ /// An invalid pointer address was detected by the underlying socket provider.
+ fault = EFAULT,
+ /// An invalid argument was supplied to a $(D_PSYMBOL Socket) member.
+ invalidArgument = EINVAL,
+ /// The limit on the number of open sockets has been reached.
+ tooManyOpenSockets = ENFILE,
+ /// No free buffer space is available for a Socket operation.
+ noBufferSpaceAvailable = ENOBUFS,
+ /// The address family is not supported by the protocol family.
+ operationNotSupported = EOPNOTSUPP,
+ /// The protocol is not implemented or has not been configured.
+ protocolNotSupported = EPROTONOSUPPORT,
+ /// Protocol error.
+ protocolError = EPROTOTYPE,
+ /// The connection attempt timed out, or the connected host has failed to respond.
+ timedOut = ETIMEDOUT,
+ /// The support for the specified socket type does not exist in this address family.
+ socketNotSupported = ESOCKTNOSUPPORT,
}
/**
@@ -590,53 +592,53 @@ enum SocketError : int
*/
class SocketException : Exception
{
- immutable SocketError error = SocketError.unknown;
-
- /**
- * Params:
- * msg = The message for the exception.
- * file = The file where the exception occurred.
- * line = The line number where the exception occurred.
- * next = The previous exception in the chain of exceptions, if any.
- */
- this(string msg,
- string file = __FILE__,
- size_t line = __LINE__,
- Throwable next = null) @nogc @safe nothrow
- {
- super(msg, file, line, next);
-
- foreach (member; EnumMembers!SocketError)
- {
- if (member == lastError)
- {
- error = member;
- return;
- }
- }
- if (lastError == ENOMEM)
- {
- error = SocketError.noBufferSpaceAvailable;
- }
- else if (lastError == EMFILE)
- {
- error = SocketError.tooManyOpenSockets;
- }
- else version (linux)
- {
- if (lastError == ENOSR)
- {
- error = SocketError.networkDown;
- }
- }
- else version (Posix)
- {
- if (lastError == EPROTO)
- {
- error = SocketError.networkDown;
- }
- }
- }
+ immutable SocketError error = SocketError.unknown;
+
+ /**
+ * Params:
+ * msg = The message for the exception.
+ * file = The file where the exception occurred.
+ * line = The line number where the exception occurred.
+ * next = The previous exception in the chain of exceptions, if any.
+ */
+ this(string msg,
+ string file = __FILE__,
+ size_t line = __LINE__,
+ Throwable next = null) @nogc @safe nothrow
+ {
+ super(msg, file, line, next);
+
+ foreach (member; EnumMembers!SocketError)
+ {
+ if (member == lastError)
+ {
+ error = member;
+ return;
+ }
+ }
+ if (lastError == ENOMEM)
+ {
+ error = SocketError.noBufferSpaceAvailable;
+ }
+ else if (lastError == EMFILE)
+ {
+ error = SocketError.tooManyOpenSockets;
+ }
+ else version (linux)
+ {
+ if (lastError == ENOSR)
+ {
+ error = SocketError.networkDown;
+ }
+ }
+ else version (Posix)
+ {
+ if (lastError == EPROTO)
+ {
+ error = SocketError.networkDown;
+ }
+ }
+ }
}
/**
@@ -645,353 +647,353 @@ class SocketException : Exception
*/
abstract class Socket
{
- version (Posix)
- {
- /**
- * How a socket is shutdown.
- */
- enum Shutdown : int
- {
- receive = SHUT_RD, /// Socket receives are disallowed
- send = SHUT_WR, /// Socket sends are disallowed
- both = SHUT_RDWR, /// Both receive and send
- }
- }
- else version (Windows)
- {
- /// Property to get or set whether the socket is blocking or nonblocking.
- private bool blocking_ = true;
-
- /**
- * How a socket is shutdown.
- */
- enum Shutdown : int
- {
- receive = SD_RECEIVE, /// Socket receives are disallowed.
- send = SD_SEND, /// Socket sends are disallowed.
- both = SD_BOTH, /// Both receive and send.
- }
-
- // The WinSock timeouts seem to be effectively skewed by a constant
- // offset of about half a second (in milliseconds).
- private enum WINSOCK_TIMEOUT_SKEW = 500;
- }
-
- /// Socket handle.
- protected socket_t handle_;
-
- /// Address family.
- protected AddressFamily family;
-
- private @property void handle(socket_t handle) @nogc
- in
- {
- assert(handle != socket_t.init);
- assert(handle_ == socket_t.init, "Socket handle cannot be changed");
- }
- body
- {
- handle_ = handle;
-
- // Set the option to disable SIGPIPE on send() if the platform
- // has it (e.g. on OS X).
- static if (is(typeof(SO_NOSIGPIPE)))
- {
- setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_NOSIGPIPE, true);
- }
- }
-
- @property inout(socket_t) handle() inout const pure nothrow @safe @nogc
- {
- return handle_;
- }
-
- /**
- * Create a socket.
- *
- * Params:
- * handle = Socket.
- * af = Address family.
- */
- this(socket_t handle, AddressFamily af) @nogc
- in
- {
- assert(handle != socket_t.init);
- }
- body
- {
- scope (failure)
- {
- this.close();
- }
- this.handle = handle;
- family = af;
- }
-
- /**
- * Closes the socket and calls the destructor on itself.
- */
- ~this() nothrow @trusted @nogc
- {
- this.close();
- }
-
- /**
- * Get a socket option.
- *
- * Params:
- * level = Protocol level at that the option exists.
- * option = Option.
- * result = Buffer to save the result.
- *
- * Returns: The number of bytes written to $(D_PARAM result).
- *
- * Throws: $(D_PSYMBOL SocketException) on error.
- */
- protected int getOption(SocketOptionLevel level,
- SocketOption option,
- void[] result) const @trusted @nogc
- {
- auto length = cast(socklen_t) result.length;
- if (getsockopt(handle_,
- cast(int) level,
- cast(int) option,
- result.ptr,
- &length) == SOCKET_ERROR)
- {
- throw defaultAllocator.make!SocketException("Unable to get socket option");
- }
- return length;
- }
-
- /// Ditto.
- int getOption(SocketOptionLevel level,
- SocketOption option,
- out size_t result) const @trusted @nogc
- {
- return getOption(level, option, (&result)[0..1]);
- }
-
- /// Ditto.
- int getOption(SocketOptionLevel level,
- SocketOption option,
+ version (Posix)
+ {
+ /**
+ * How a socket is shutdown.
+ */
+ enum Shutdown : int
+ {
+ receive = SHUT_RD, /// Socket receives are disallowed
+ send = SHUT_WR, /// Socket sends are disallowed
+ both = SHUT_RDWR, /// Both receive and send
+ }
+ }
+ else version (Windows)
+ {
+ /// Property to get or set whether the socket is blocking or nonblocking.
+ private bool blocking_ = true;
+
+ /**
+ * How a socket is shutdown.
+ */
+ enum Shutdown : int
+ {
+ receive = SD_RECEIVE, /// Socket receives are disallowed.
+ send = SD_SEND, /// Socket sends are disallowed.
+ both = SD_BOTH, /// Both receive and send.
+ }
+
+ // The WinSock timeouts seem to be effectively skewed by a constant
+ // offset of about half a second (in milliseconds).
+ private enum WINSOCK_TIMEOUT_SKEW = 500;
+ }
+
+ /// Socket handle.
+ protected socket_t handle_;
+
+ /// Address family.
+ protected AddressFamily family;
+
+ private @property void handle(socket_t handle) @nogc
+ in
+ {
+ assert(handle != socket_t.init);
+ assert(handle_ == socket_t.init, "Socket handle cannot be changed");
+ }
+ body
+ {
+ handle_ = handle;
+
+ // Set the option to disable SIGPIPE on send() if the platform
+ // has it (e.g. on OS X).
+ static if (is(typeof(SO_NOSIGPIPE)))
+ {
+ setOption(SocketOptionLevel.SOCKET, cast(SocketOption)SO_NOSIGPIPE, true);
+ }
+ }
+
+ @property inout(socket_t) handle() inout const pure nothrow @safe @nogc
+ {
+ return handle_;
+ }
+
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * handle = Socket.
+ * af = Address family.
+ */
+ this(socket_t handle, AddressFamily af) @nogc
+ in
+ {
+ assert(handle != socket_t.init);
+ }
+ body
+ {
+ scope (failure)
+ {
+ this.close();
+ }
+ this.handle = handle;
+ family = af;
+ }
+
+ /**
+ * Closes the socket and calls the destructor on itself.
+ */
+ ~this() nothrow @trusted @nogc
+ {
+ this.close();
+ }
+
+ /**
+ * Get a socket option.
+ *
+ * Params:
+ * level = Protocol level at that the option exists.
+ * option = Option.
+ * result = Buffer to save the result.
+ *
+ * Returns: The number of bytes written to $(D_PARAM result).
+ *
+ * Throws: $(D_PSYMBOL SocketException) on error.
+ */
+ protected int getOption(SocketOptionLevel level,
+ SocketOption option,
+ void[] result) const @trusted @nogc
+ {
+ auto length = cast(socklen_t) result.length;
+ if (getsockopt(handle_,
+ cast(int) level,
+ cast(int) option,
+ result.ptr,
+ &length) == SOCKET_ERROR)
+ {
+ throw defaultAllocator.make!SocketException("Unable to get socket option");
+ }
+ return length;
+ }
+
+ /// Ditto.
+ int getOption(SocketOptionLevel level,
+ SocketOption option,
+ out size_t result) const @trusted @nogc
+ {
+ return getOption(level, option, (&result)[0..1]);
+ }
+
+ /// Ditto.
+ int getOption(SocketOptionLevel level,
+ SocketOption option,
out Linger result) const @trusted @nogc
- {
- return getOption(level, option, (&result.clinger)[0..1]);
- }
-
- /// Ditto.
- int getOption(SocketOptionLevel level,
- SocketOption option,
- out Duration result) const @trusted @nogc
- {
- // WinSock returns the timeout values as a milliseconds DWORD,
- // while Linux and BSD return a timeval struct.
- version (Posix)
- {
- timeval tv;
- auto ret = getOption(level, option, (&tv)[0..1]);
- result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec);
- }
- else version (Windows)
- {
- int msecs;
- auto ret = getOption(level, option, (&msecs)[0 .. 1]);
- if (option == SocketOption.RCVTIMEO)
- {
- msecs += WINSOCK_TIMEOUT_SKEW;
- }
- result = dur!"msecs"(msecs);
- }
- return ret;
- }
-
- /**
- * Set a socket option.
- *
- * Params:
- * level = Protocol level at that the option exists.
- * option = Option.
- * value = Option value.
- *
- * Throws: $(D_PSYMBOL SocketException) on error.
- */
- protected void setOption(SocketOptionLevel level,
- SocketOption option,
- void[] value) const @trusted @nogc
- {
- if (setsockopt(handle_,
- cast(int)level,
- cast(int)option,
- value.ptr,
- cast(uint) value.length) == SOCKET_ERROR)
- {
- throw defaultAllocator.make!SocketException("Unable to set socket option");
- }
- }
-
- /// Ditto.
- void setOption(SocketOptionLevel level, SocketOption option, size_t value)
- const @trusted @nogc
- {
- setOption(level, option, (&value)[0..1]);
- }
-
- /// Ditto.
- void setOption(SocketOptionLevel level, SocketOption option, Linger value)
- const @trusted @nogc
- {
- setOption(level, option, (&value.clinger)[0..1]);
- }
-
- /// Ditto.
- void setOption(SocketOptionLevel level, SocketOption option, Duration value)
- const @trusted @nogc
- {
- version (Posix)
- {
- timeval tv;
- value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec);
- setOption(level, option, (&tv)[0..1]);
- }
- else version (Windows)
- {
- auto msecs = cast(int) value.total!"msecs";
- if (msecs > 0 && option == SocketOption.RCVTIMEO)
- {
- msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW);
- }
- setOption(level, option, msecs);
- }
- }
-
- /**
- * Returns: Socket's blocking flag.
- */
- @property inout(bool) blocking() inout const nothrow @nogc
- {
- version (Posix)
- {
- return !(fcntl(handle_, F_GETFL, 0) & O_NONBLOCK);
- }
- else version (Windows)
- {
- return blocking_;
- }
- }
-
- /**
- * Params:
- * yes = Socket's blocking flag.
- */
- @property void blocking(bool yes) @nogc
- {
- version (Posix)
- {
- int fl = fcntl(handle_, F_GETFL, 0);
-
- if (fl != SOCKET_ERROR)
- {
- fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK;
- fl = fcntl(handle_, F_SETFL, fl);
- }
- if (fl == SOCKET_ERROR)
- {
- throw make!SocketException(defaultAllocator,
- "Unable to set socket blocking");
- }
- }
- else version (Windows)
- {
- uint num = !yes;
- if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
- {
- throw make!SocketException(defaultAllocator,
- "Unable to set socket blocking");
- }
- blocking_ = yes;
- }
- }
-
- /**
- * Returns: The socket's address family.
- */
- @property AddressFamily addressFamily() const @nogc @safe pure nothrow
- {
- return family;
- }
-
- /**
- * Returns: $(D_KEYWORD true) if this is a valid, alive socket.
- */
- @property bool isAlive() @trusted const nothrow @nogc
- {
- int type;
- socklen_t typesize = cast(socklen_t) type.sizeof;
- return !getsockopt(handle_, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize);
- }
-
- /**
- * Disables sends and/or receives.
- *
- * Params:
- * how = What to disable.
- *
- * See_Also:
- * $(D_PSYMBOL Shutdown)
- */
- void shutdown(Shutdown how = Shutdown.both) @nogc @trusted const nothrow
- {
- .shutdown(handle_, cast(int)how);
- }
-
- /**
- * Immediately drop any connections and release socket resources.
- * Calling $(D_PSYMBOL shutdown) before $(D_PSYMBOL close) is recommended
- * for connection-oriented sockets. The $(D_PSYMBOL Socket) object is no
- * longer usable after $(D_PSYMBOL close).
- */
- void close() nothrow @trusted @nogc
- {
- version(Windows)
- {
- .closesocket(handle_);
- }
- else version(Posix)
- {
- .close(handle_);
- }
- handle_ = socket_t.init;
- }
-
- /**
- * Listen for an incoming connection. $(D_PSYMBOL bind) must be called before you
- * can $(D_PSYMBOL listen).
- *
- * Params:
- * backlog = Request of how many pending incoming connections are
- * queued until $(D_PSYMBOL accept)ed.
- */
- void listen(int backlog) const @trusted @nogc
- {
- if (.listen(handle_, backlog) == SOCKET_ERROR)
- {
- throw defaultAllocator.make!SocketException("Unable to listen on socket");
- }
- }
-
- /**
- * Compare handles.
- *
- * Params:
- * that = Another handle.
- *
- * Returns: Comparision result.
- */
- int opCmp(size_t that) const pure nothrow @safe @nogc
- {
- return handle_ < that ? -1 : handle_ > that ? 1 : 0;
- }
+ {
+ return getOption(level, option, (&result.clinger)[0..1]);
+ }
+
+ /// Ditto.
+ int getOption(SocketOptionLevel level,
+ SocketOption option,
+ out Duration result) const @trusted @nogc
+ {
+ // WinSock returns the timeout values as a milliseconds DWORD,
+ // while Linux and BSD return a timeval struct.
+ version (Posix)
+ {
+ timeval tv;
+ auto ret = getOption(level, option, (&tv)[0..1]);
+ result = dur!"seconds"(tv.tv_sec) + dur!"usecs"(tv.tv_usec);
+ }
+ else version (Windows)
+ {
+ int msecs;
+ auto ret = getOption(level, option, (&msecs)[0 .. 1]);
+ if (option == SocketOption.RCVTIMEO)
+ {
+ msecs += WINSOCK_TIMEOUT_SKEW;
+ }
+ result = dur!"msecs"(msecs);
+ }
+ return ret;
+ }
+
+ /**
+ * Set a socket option.
+ *
+ * Params:
+ * level = Protocol level at that the option exists.
+ * option = Option.
+ * value = Option value.
+ *
+ * Throws: $(D_PSYMBOL SocketException) on error.
+ */
+ protected void setOption(SocketOptionLevel level,
+ SocketOption option,
+ void[] value) const @trusted @nogc
+ {
+ if (setsockopt(handle_,
+ cast(int)level,
+ cast(int)option,
+ value.ptr,
+ cast(uint) value.length) == SOCKET_ERROR)
+ {
+ throw defaultAllocator.make!SocketException("Unable to set socket option");
+ }
+ }
+
+ /// Ditto.
+ void setOption(SocketOptionLevel level, SocketOption option, size_t value)
+ const @trusted @nogc
+ {
+ setOption(level, option, (&value)[0..1]);
+ }
+
+ /// Ditto.
+ void setOption(SocketOptionLevel level, SocketOption option, Linger value)
+ const @trusted @nogc
+ {
+ setOption(level, option, (&value.clinger)[0..1]);
+ }
+
+ /// Ditto.
+ void setOption(SocketOptionLevel level, SocketOption option, Duration value)
+ const @trusted @nogc
+ {
+ version (Posix)
+ {
+ timeval tv;
+ value.split!("seconds", "usecs")(tv.tv_sec, tv.tv_usec);
+ setOption(level, option, (&tv)[0..1]);
+ }
+ else version (Windows)
+ {
+ auto msecs = cast(int) value.total!"msecs";
+ if (msecs > 0 && option == SocketOption.RCVTIMEO)
+ {
+ msecs = max(1, msecs - WINSOCK_TIMEOUT_SKEW);
+ }
+ setOption(level, option, msecs);
+ }
+ }
+
+ /**
+ * Returns: Socket's blocking flag.
+ */
+ @property inout(bool) blocking() inout const nothrow @nogc
+ {
+ version (Posix)
+ {
+ return !(fcntl(handle_, F_GETFL, 0) & O_NONBLOCK);
+ }
+ else version (Windows)
+ {
+ return blocking_;
+ }
+ }
+
+ /**
+ * Params:
+ * yes = Socket's blocking flag.
+ */
+ @property void blocking(bool yes) @nogc
+ {
+ version (Posix)
+ {
+ int fl = fcntl(handle_, F_GETFL, 0);
+
+ if (fl != SOCKET_ERROR)
+ {
+ fl = yes ? fl & ~O_NONBLOCK : fl | O_NONBLOCK;
+ fl = fcntl(handle_, F_SETFL, fl);
+ }
+ if (fl == SOCKET_ERROR)
+ {
+ throw make!SocketException(defaultAllocator,
+ "Unable to set socket blocking");
+ }
+ }
+ else version (Windows)
+ {
+ uint num = !yes;
+ if (ioctlsocket(handle_, FIONBIO, &num) == SOCKET_ERROR)
+ {
+ throw make!SocketException(defaultAllocator,
+ "Unable to set socket blocking");
+ }
+ blocking_ = yes;
+ }
+ }
+
+ /**
+ * Returns: The socket's address family.
+ */
+ @property AddressFamily addressFamily() const @nogc @safe pure nothrow
+ {
+ return family;
+ }
+
+ /**
+ * Returns: $(D_KEYWORD true) if this is a valid, alive socket.
+ */
+ @property bool isAlive() @trusted const nothrow @nogc
+ {
+ int type;
+ socklen_t typesize = cast(socklen_t) type.sizeof;
+ return !getsockopt(handle_, SOL_SOCKET, SO_TYPE, cast(char*)&type, &typesize);
+ }
+
+ /**
+ * Disables sends and/or receives.
+ *
+ * Params:
+ * how = What to disable.
+ *
+ * See_Also:
+ * $(D_PSYMBOL Shutdown)
+ */
+ void shutdown(Shutdown how = Shutdown.both) @nogc @trusted const nothrow
+ {
+ .shutdown(handle_, cast(int)how);
+ }
+
+ /**
+ * Immediately drop any connections and release socket resources.
+ * Calling $(D_PSYMBOL shutdown) before $(D_PSYMBOL close) is recommended
+ * for connection-oriented sockets. The $(D_PSYMBOL Socket) object is no
+ * longer usable after $(D_PSYMBOL close).
+ */
+ void close() nothrow @trusted @nogc
+ {
+ version(Windows)
+ {
+ .closesocket(handle_);
+ }
+ else version(Posix)
+ {
+ .close(handle_);
+ }
+ handle_ = socket_t.init;
+ }
+
+ /**
+ * Listen for an incoming connection. $(D_PSYMBOL bind) must be called before you
+ * can $(D_PSYMBOL listen).
+ *
+ * Params:
+ * backlog = Request of how many pending incoming connections are
+ * queued until $(D_PSYMBOL accept)ed.
+ */
+ void listen(int backlog) const @trusted @nogc
+ {
+ if (.listen(handle_, backlog) == SOCKET_ERROR)
+ {
+ throw defaultAllocator.make!SocketException("Unable to listen on socket");
+ }
+ }
+
+ /**
+ * Compare handles.
+ *
+ * Params:
+ * that = Another handle.
+ *
+ * Returns: Comparision result.
+ */
+ int opCmp(size_t that) const pure nothrow @safe @nogc
+ {
+ return handle_ < that ? -1 : handle_ > that ? 1 : 0;
+ }
}
/**
@@ -999,119 +1001,123 @@ abstract class Socket
*/
interface ConnectionOrientedSocket
{
- /**
- * Flags may be OR'ed together.
- */
- enum Flag : int
- {
- none = 0, /// No flags specified
- outOfBand = MSG_OOB, /// Out-of-band stream data
- peek = MSG_PEEK, /// Peek at incoming data without removing it from the queue, only for receiving
- dontRoute = MSG_DONTROUTE, /// Data should not be subject to routing; this flag may be ignored. Only for sending
- }
-
- alias Flags = BitFlags!Flag;
+ /**
+ * Flags may be OR'ed together.
+ */
+ enum Flag : int
+ {
+ /// No flags specified.
+ none = 0,
+ /// Out-of-band stream data.
+ outOfBand = MSG_OOB,
+ /// Peek at incoming data without removing it from the queue, only for receiving.
+ peek = MSG_PEEK,
+ /// Data should not be subject to routing; this flag may be ignored. Only for sending.
+ dontRoute = MSG_DONTROUTE,
+ }
+
+ alias Flags = BitFlags!Flag;
}
class StreamSocket : Socket, ConnectionOrientedSocket
{
- /**
- * Create a socket.
- *
- * Params:
- * af = Address family.
- */
- this(AddressFamily af) @trusted @nogc
- {
- auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
- if (handle == socket_t.init)
- {
- throw defaultAllocator.make!SocketException("Unable to create socket");
- }
- super(handle, af);
- }
-
- /**
- * Associate a local address with this socket.
- *
- * Params:
- * address = Local address.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to bind.
- */
- void bind(Address address) const @trusted @nogc
- {
- if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
- {
- throw defaultAllocator.make!SocketException("Unable to bind socket");
- }
- }
-
- /**
- * Accept an incoming connection.
- *
- * The blocking mode is always inherited.
- *
- * Returns: $(D_PSYMBOL Socket) for the accepted connection or
- * $(D_KEYWORD null) if the call would block on a
- * non-blocking socket.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to accept.
- */
- ConnectedSocket accept() @trusted @nogc
- {
- socket_t sock;
-
- version (linux)
- {
- int flags;
- if (!blocking)
- {
- flags |= SOCK_NONBLOCK;
- }
- sock = cast(socket_t).accept4(handle_, null, null, flags);
- }
- else
- {
- sock = cast(socket_t).accept(handle_, null, null);
- }
-
- if (sock == socket_t.init)
- {
- if (wouldHaveBlocked())
- {
- return null;
- }
- throw make!SocketException(defaultAllocator,
- "Unable to accept socket connection");
- }
-
- auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily);
-
- version (linux)
- { // Blocking mode already set
- }
- else version (Posix)
- {
- if (!blocking)
- {
- try
- {
- newSocket.blocking = blocking;
- }
- catch (SocketException e)
- {
- defaultAllocator.dispose(newSocket);
- throw e;
- }
- }
- }
- else version (Windows)
- { // Inherits blocking mode
- newSocket.blocking_ = blocking;
- }
- return newSocket;
- }
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * af = Address family.
+ */
+ this(AddressFamily af) @trusted @nogc
+ {
+ auto handle = cast(socket_t) socket(af, SOCK_STREAM, 0);
+ if (handle == socket_t.init)
+ {
+ throw defaultAllocator.make!SocketException("Unable to create socket");
+ }
+ super(handle, af);
+ }
+
+ /**
+ * Associate a local address with this socket.
+ *
+ * Params:
+ * address = Local address.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to bind.
+ */
+ void bind(Address address) const @trusted @nogc
+ {
+ if (.bind(handle_, address.name, address.length) == SOCKET_ERROR)
+ {
+ throw defaultAllocator.make!SocketException("Unable to bind socket");
+ }
+ }
+
+ /**
+ * Accept an incoming connection.
+ *
+ * The blocking mode is always inherited.
+ *
+ * Returns: $(D_PSYMBOL Socket) for the accepted connection or
+ * $(D_KEYWORD null) if the call would block on a
+ * non-blocking socket.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to accept.
+ */
+ ConnectedSocket accept() @trusted @nogc
+ {
+ socket_t sock;
+
+ version (linux)
+ {
+ int flags;
+ if (!blocking)
+ {
+ flags |= SOCK_NONBLOCK;
+ }
+ sock = cast(socket_t).accept4(handle_, null, null, flags);
+ }
+ else
+ {
+ sock = cast(socket_t).accept(handle_, null, null);
+ }
+
+ if (sock == socket_t.init)
+ {
+ if (wouldHaveBlocked())
+ {
+ return null;
+ }
+ throw make!SocketException(defaultAllocator,
+ "Unable to accept socket connection");
+ }
+
+ auto newSocket = defaultAllocator.make!ConnectedSocket(sock, addressFamily);
+
+ version (linux)
+ { // Blocking mode already set
+ }
+ else version (Posix)
+ {
+ if (!blocking)
+ {
+ try
+ {
+ newSocket.blocking = blocking;
+ }
+ catch (SocketException e)
+ {
+ defaultAllocator.dispose(newSocket);
+ throw e;
+ }
+ }
+ }
+ else version (Windows)
+ { // Inherits blocking mode
+ newSocket.blocking_ = blocking;
+ }
+ return newSocket;
+ }
}
/**
@@ -1119,124 +1125,124 @@ class StreamSocket : Socket, ConnectionOrientedSocket
*/
class ConnectedSocket : Socket, ConnectionOrientedSocket
{
- /**
- * $(D_KEYWORD true) if the stream socket peer has performed an orderly
- * shutdown.
- */
- protected bool disconnected_;
-
- /**
- * Returns: $(D_KEYWORD true) if the stream socket peer has performed an orderly
- * shutdown.
- */
- @property inout(bool) disconnected() inout const pure nothrow @safe @nogc
- {
- return disconnected_;
- }
-
- /**
- * Create a socket.
- *
- * Params:
- * handle = Socket.
- * af = Address family.
- */
- this(socket_t handle, AddressFamily af) @nogc
- {
- super(handle, af);
- }
-
- version (Windows)
- {
- private static int capToMaxBuffer(size_t size) pure nothrow @safe @nogc
- {
- // Windows uses int instead of size_t for length arguments.
- // Luckily, the send/recv functions make no guarantee that
- // all the data is sent, so we use that to send at most
- // int.max bytes.
- return size > size_t (int.max) ? int.max : cast(int) size;
- }
- }
- else
- {
- private static size_t capToMaxBuffer(size_t size) pure nothrow @safe @nogc
- {
- return size;
- }
- }
-
- /**
- * Receive data on the connection.
- *
- * Params:
- * buf = Buffer to save received data.
- * flags = Flags.
- *
- * Returns: The number of bytes received or 0 if nothing received
- * because the call would block.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to receive.
- */
- ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted @nogc
- {
- ptrdiff_t ret;
- if (!buf.length)
- {
- return 0;
- }
-
- ret = recv(handle_, buf.ptr, capToMaxBuffer(buf.length), cast(int) flags);
- if (ret == 0)
- {
- disconnected_ = true;
- }
- else if (ret == SOCKET_ERROR)
- {
- if (wouldHaveBlocked())
- {
- return 0;
- }
- disconnected_ = true;
- throw defaultAllocator.make!SocketException("Unable to receive");
- }
- return ret;
- }
-
- /**
- * Send data on the connection. If the socket is blocking and there is no
- * buffer space left, $(D_PSYMBOL send) waits, non-blocking socket returns
- * 0 in this case.
- *
- * Params:
- * buf = Data to be sent.
- * flags = Flags.
- *
- * Returns: The number of bytes actually sent.
- *
- * Throws: $(D_PSYMBOL SocketException) if unable to send.
- */
- ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none)
- const @trusted @nogc
- {
- int sendFlags = cast(int) flags;
- ptrdiff_t sent;
-
- static if (is(typeof(MSG_NOSIGNAL)))
- {
- sendFlags |= MSG_NOSIGNAL;
- }
-
- sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags);
- if (sent != SOCKET_ERROR)
- {
- return sent;
- }
- else if (wouldHaveBlocked())
- {
- return 0;
- }
- throw defaultAllocator.make!SocketException("Unable to send");
- }
+ /**
+ * $(D_KEYWORD true) if the stream socket peer has performed an orderly
+ * shutdown.
+ */
+ protected bool disconnected_;
+
+ /**
+ * Returns: $(D_KEYWORD true) if the stream socket peer has performed an orderly
+ * shutdown.
+ */
+ @property inout(bool) disconnected() inout const pure nothrow @safe @nogc
+ {
+ return disconnected_;
+ }
+
+ /**
+ * Create a socket.
+ *
+ * Params:
+ * handle = Socket.
+ * af = Address family.
+ */
+ this(socket_t handle, AddressFamily af) @nogc
+ {
+ super(handle, af);
+ }
+
+ version (Windows)
+ {
+ private static int capToMaxBuffer(size_t size) pure nothrow @safe @nogc
+ {
+ // Windows uses int instead of size_t for length arguments.
+ // Luckily, the send/recv functions make no guarantee that
+ // all the data is sent, so we use that to send at most
+ // int.max bytes.
+ return size > size_t (int.max) ? int.max : cast(int) size;
+ }
+ }
+ else
+ {
+ private static size_t capToMaxBuffer(size_t size) pure nothrow @safe @nogc
+ {
+ return size;
+ }
+ }
+
+ /**
+ * Receive data on the connection.
+ *
+ * Params:
+ * buf = Buffer to save received data.
+ * flags = Flags.
+ *
+ * Returns: The number of bytes received or 0 if nothing received
+ * because the call would block.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to receive.
+ */
+ ptrdiff_t receive(ubyte[] buf, Flags flags = Flag.none) @trusted @nogc
+ {
+ ptrdiff_t ret;
+ if (!buf.length)
+ {
+ return 0;
+ }
+
+ ret = recv(handle_, buf.ptr, capToMaxBuffer(buf.length), cast(int) flags);
+ if (ret == 0)
+ {
+ disconnected_ = true;
+ }
+ else if (ret == SOCKET_ERROR)
+ {
+ if (wouldHaveBlocked())
+ {
+ return 0;
+ }
+ disconnected_ = true;
+ throw defaultAllocator.make!SocketException("Unable to receive");
+ }
+ return ret;
+ }
+
+ /**
+ * Send data on the connection. If the socket is blocking and there is no
+ * buffer space left, $(D_PSYMBOL send) waits, non-blocking socket returns
+ * 0 in this case.
+ *
+ * Params:
+ * buf = Data to be sent.
+ * flags = Flags.
+ *
+ * Returns: The number of bytes actually sent.
+ *
+ * Throws: $(D_PSYMBOL SocketException) if unable to send.
+ */
+ ptrdiff_t send(const(ubyte)[] buf, Flags flags = Flag.none)
+ const @trusted @nogc
+ {
+ int sendFlags = cast(int) flags;
+ ptrdiff_t sent;
+
+ static if (is(typeof(MSG_NOSIGNAL)))
+ {
+ sendFlags |= MSG_NOSIGNAL;
+ }
+
+ sent = .send(handle_, buf.ptr, capToMaxBuffer(buf.length), sendFlags);
+ if (sent != SOCKET_ERROR)
+ {
+ return sent;
+ }
+ else if (wouldHaveBlocked())
+ {
+ return 0;
+ }
+ throw defaultAllocator.make!SocketException("Unable to send");
+ }
}
/**
@@ -1244,132 +1250,132 @@ class ConnectedSocket : Socket, ConnectionOrientedSocket
*/
abstract class Address
{
- /**
- * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
- */
- abstract @property inout(sockaddr)* name() inout pure nothrow @nogc;
-
- /**
- * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
- */
- abstract @property inout(socklen_t) length() inout const pure nothrow @nogc;
+ /**
+ * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
+ */
+ abstract @property inout(sockaddr)* name() inout pure nothrow @nogc;
+
+ /**
+ * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
+ */
+ abstract @property inout(socklen_t) length() inout const pure nothrow @nogc;
}
class InternetAddress : Address
{
- version (Windows)
- {
- /// Internal internet address representation.
- protected SOCKADDR_STORAGE storage;
- }
- else version (Posix)
- {
- /// Internal internet address representation.
- protected sockaddr_storage storage;
- }
- immutable ushort port_;
-
- enum
- {
- anyPort = 0,
- }
-
- this(in string host, ushort port = anyPort) @nogc
- {
- if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
- {
- throw make!SocketException(defaultAllocator,
- "Address info lookup is not available on this system");
- }
- addrinfo* ai_res;
- port_ = port;
-
- // Make C-string from host.
- auto node = cast(char[]) allocator.allocate(host.length + 1);
- node[0.. $ - 1] = host;
- node[$ - 1] = '\0';
- scope (exit)
- {
- allocator.deallocate(node);
- }
-
- // Convert port to a C-string.
- char[6] service = [0, 0, 0, 0, 0, 0];
- const(char)* servicePointer;
- if (port)
- {
- ushort start;
- for (ushort j = 10, i = 4; i > 0; j *= 10, --i)
- {
- ushort rest = port % 10;
- if (rest != 0)
- {
- service[i] = cast(char) (rest + '0');
- start = i;
- }
- port /= 10;
- }
- servicePointer = service[start..$].ptr;
- }
-
- auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
- if (ret)
- {
- throw defaultAllocator.make!SocketException("Address info lookup failed");
- }
- scope (exit)
- {
- freeaddrinfoPointer(ai_res);
- }
-
- ubyte* dp = cast(ubyte*) &storage, sp = cast(ubyte*) ai_res.ai_addr;
- for (auto i = ai_res.ai_addrlen; i > 0; --i, *dp++, *sp++)
- {
- *dp = *sp;
- }
- if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)
- {
- throw defaultAllocator.make!SocketException("Wrong address family");
- }
- }
-
- /**
- * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
- */
- override @property inout(sockaddr)* name() inout pure nothrow @nogc
- {
- return cast(sockaddr*) &storage;
- }
-
- /**
- * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
- */
- override @property inout(socklen_t) length() inout const pure nothrow @nogc
- {
- // FreeBSD wants to know the exact length of the address on bind.
- switch (family)
- {
- case AddressFamily.INET:
- return sockaddr_in.sizeof;
- case AddressFamily.INET6:
- return sockaddr_in6.sizeof;
- default:
- assert(false);
- }
- }
-
- /**
- * Returns: Family of this address.
- */
- @property inout(AddressFamily) family() inout const pure nothrow @nogc
- {
- return cast(AddressFamily) storage.ss_family;
- }
-
- @property inout(ushort) port() inout const pure nothrow @nogc
- {
- return port_;
- }
+ version (Windows)
+ {
+ /// Internal internet address representation.
+ protected SOCKADDR_STORAGE storage;
+ }
+ else version (Posix)
+ {
+ /// Internal internet address representation.
+ protected sockaddr_storage storage;
+ }
+ immutable ushort port_;
+
+ enum
+ {
+ anyPort = 0,
+ }
+
+ this(in string host, ushort port = anyPort) @nogc
+ {
+ if (getaddrinfoPointer is null || freeaddrinfoPointer is null)
+ {
+ throw make!SocketException(defaultAllocator,
+ "Address info lookup is not available on this system");
+ }
+ addrinfo* ai_res;
+ port_ = port;
+
+ // Make C-string from host.
+ auto node = cast(char[]) allocator.allocate(host.length + 1);
+ node[0.. $ - 1] = host;
+ node[$ - 1] = '\0';
+ scope (exit)
+ {
+ allocator.deallocate(node);
+ }
+
+ // Convert port to a C-string.
+ char[6] service = [0, 0, 0, 0, 0, 0];
+ const(char)* servicePointer;
+ if (port)
+ {
+ ushort start;
+ for (ushort j = 10, i = 4; i > 0; j *= 10, --i)
+ {
+ ushort rest = port % 10;
+ if (rest != 0)
+ {
+ service[i] = cast(char) (rest + '0');
+ start = i;
+ }
+ port /= 10;
+ }
+ servicePointer = service[start..$].ptr;
+ }
+
+ auto ret = getaddrinfoPointer(node.ptr, servicePointer, null, &ai_res);
+ if (ret)
+ {
+ throw defaultAllocator.make!SocketException("Address info lookup failed");
+ }
+ scope (exit)
+ {
+ freeaddrinfoPointer(ai_res);
+ }
+
+ ubyte* dp = cast(ubyte*) &storage, sp = cast(ubyte*) ai_res.ai_addr;
+ for (auto i = ai_res.ai_addrlen; i > 0; --i, *dp++, *sp++)
+ {
+ *dp = *sp;
+ }
+ if (ai_res.ai_family != AddressFamily.INET && ai_res.ai_family != AddressFamily.INET6)
+ {
+ throw defaultAllocator.make!SocketException("Wrong address family");
+ }
+ }
+
+ /**
+ * Returns: Pointer to underlying $(D_PSYMBOL sockaddr) structure.
+ */
+ override @property inout(sockaddr)* name() inout pure nothrow @nogc
+ {
+ return cast(sockaddr*) &storage;
+ }
+
+ /**
+ * Returns: Actual size of underlying $(D_PSYMBOL sockaddr) structure.
+ */
+ override @property inout(socklen_t) length() inout const pure nothrow @nogc
+ {
+ // FreeBSD wants to know the exact length of the address on bind.
+ switch (family)
+ {
+ case AddressFamily.INET:
+ return sockaddr_in.sizeof;
+ case AddressFamily.INET6:
+ return sockaddr_in6.sizeof;
+ default:
+ assert(false);
+ }
+ }
+
+ /**
+ * Returns: Family of this address.
+ */
+ @property inout(AddressFamily) family() inout const pure nothrow @nogc
+ {
+ return cast(AddressFamily) storage.ss_family;
+ }
+
+ @property inout(ushort) port() inout const pure nothrow @nogc
+ {
+ return port_;
+ }
}
/**
@@ -1383,16 +1389,16 @@ class InternetAddress : Address
*/
bool wouldHaveBlocked() nothrow @trusted @nogc
{
- version (Posix)
- {
- return errno == EAGAIN || errno == EWOULDBLOCK;
- }
- else version (Windows)
- {
- return WSAGetLastError() == ERROR_IO_PENDING
- || WSAGetLastError() == EWOULDBLOCK
- || WSAGetLastError() == ERROR_IO_INCOMPLETE;
- }
+ version (Posix)
+ {
+ return errno == EAGAIN || errno == EWOULDBLOCK;
+ }
+ else version (Windows)
+ {
+ return WSAGetLastError() == ERROR_IO_PENDING
+ || WSAGetLastError() == EWOULDBLOCK
+ || WSAGetLastError() == ERROR_IO_INCOMPLETE;
+ }
}
/**
@@ -1400,12 +1406,12 @@ bool wouldHaveBlocked() nothrow @trusted @nogc
*/
private @property int lastError() nothrow @safe @nogc
{
- version (Windows)
- {
- return WSAGetLastError();
- }
- else
- {
- return errno;
- }
+ version (Windows)
+ {
+ return WSAGetLastError();
+ }
+ else
+ {
+ return errno;
+ }
}
diff --git a/source/tanya/network/url.d b/source/tanya/network/url.d
index d0377d4..0cbec34 100644
--- a/source/tanya/network/url.d
+++ b/source/tanya/network/url.d
@@ -3,10 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
- * Copyright: Eugene Wissner 2016.
+ * 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:belka@caraus.de, Eugene Wissner)
+ * Authors: $(LINK2 mailto:info@caraus.de, Eugene Wissner)
*/
module tanya.network.url;
@@ -18,8 +20,8 @@ import tanya.memory;
version (unittest) private
{
- import std.typecons;
- static Tuple!(string, string[string], ushort)[] URLTests;
+ import std.typecons;
+ static Tuple!(string, string[string], ushort)[] URLTests;
}
static this()
@@ -632,445 +634,445 @@ static this()
*/
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;
- }
-}
+ /** 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;
- }
+ /**
+ * 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");
+ 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]);
- }
- }
- }
+ 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]);
+ }
+ }
+ }
}
/**
@@ -1079,111 +1081,111 @@ private unittest
*/
enum Component : string
{
- scheme = "scheme",
- host = "host",
- port = "port",
- user = "user",
- pass = "pass",
- path = "path",
- query = "query",
- fragment = "fragment",
+ 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.
+ * 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);
+ 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")
+ if (T == "scheme"
+ || T == "host"
+ || T == "user"
+ || T == "pass"
+ || T == "path"
+ || T == "query"
+ || T == "fragment")
{
- auto ret = URL(source);
- return mixin("ret." ~ T);
+ auto ret = URL(source);
+ return mixin("ret." ~ T);
}
/// Ditto.
ushort parseURL(immutable(char)[] T)(in char[] source)
- if (T == "port")
+ if (T == "port")
{
- auto ret = URL(source);
- return ret.port;
+ auto ret = URL(source);
+ return ret.port;
}
unittest
{
- assert(parseURL!(Component.port)("http://example.org:5326") == 5326);
+ 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]);
- }
- }
- }
+ 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]);
+ }
+ }
+ }
}