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; + 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; + 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; + 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. - } + 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; - } + 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); + 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; + private alias GROUP = uint; - enum - { - WSA_FLAG_OVERLAPPED = 0x01, - MAX_PROTOCOL_CHAIN = 7, - WSAPROTOCOL_LEN = 255, - } + 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 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*; + 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; + 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]}; + 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 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*; + 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 SO_UPDATE_ACCEPT_CONTEXT = 0x700B; - enum OverlappedSocketEvent - { - accept = 1, - read = 2, - write = 3, - } + enum OverlappedSocketEvent + { + accept = 1, + read = 2, + write = 3, + } - class SocketState : State - { - private WSABUF buffer; - } + 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); - } + /** + * 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; + /** + * 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; + 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); + 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; - } + 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; - } + /** + * 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; + /** + * 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); + 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; - } + 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; - } - } + /** + * 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; + 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; + /** + * 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; + 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"); - } - } + 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; + /** + * 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; + 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; - } + // 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; - } - } + /** + * 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"); + 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; - } + 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; + 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); + /** + * 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; - } - } - } + 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; + 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. - } + /** + * 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; - } + // 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_; + /// Socket handle. + protected socket_t handle_; - /// Address family. - protected AddressFamily family; + /// 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; + 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); - } - } + // 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_; - } + @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; - } + /** + * 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(); - } + /** + * 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; - } + /** + * 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 size_t result) const @trusted @nogc + { + return getOption(level, option, (&result)[0..1]); + } - /// Ditto. - int getOption(SocketOptionLevel level, - SocketOption option, + /// Ditto. + int getOption(SocketOptionLevel level, + SocketOption option, out Linger result) const @trusted @nogc - { - return getOption(level, option, (&result.clinger)[0..1]); - } + { + 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; - } + /// 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"); - } - } + /** + * 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, 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, 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); - } - } + /// 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_; - } - } + /** + * 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); + /** + * 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; - } - } + 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: 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); - } + /** + * 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); - } + /** + * 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; - } + /** + * 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"); - } - } + /** + * 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; - } + /** + * 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 - } + /** + * 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; + 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); - } + /** + * 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"); - } - } + /** + * 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; + /** + * 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); - } + 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"); - } + 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); + 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; - } + 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_; + /** + * $(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_; - } + /** + * 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); - } + /** + * 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; - } - } + 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; - } + /** + * 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; - } + 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; + /** + * 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; - } + 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"); - } + 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: 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: 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_; + 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, - } + 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; + 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); - } + // 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; - } + // 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"); - } - } + 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: 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: 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; - } + /** + * 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_; - } + @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 URL scheme. */ + const(char)[] scheme; - /** The username. */ - const(char)[] user; + /** The username. */ + const(char)[] user; - /** The password. */ - const(char)[] pass; + /** The password. */ + const(char)[] pass; - /** The hostname. */ - const(char)[] host; + /** The hostname. */ + const(char)[] host; - /** The port number. */ - ushort port; + /** The port number. */ + ushort port; - /** The path. */ - const(char)[] path; + /** The path. */ + const(char)[] path; - /** The query string. */ - const(char)[] query; + /** The query string. */ + const(char)[] query; - /** The anchor. */ - const(char)[] fragment; + /** 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; + /** + * 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; - } - } + 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; - } - } + // 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 + 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; - } + 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; - } - } + // 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]; - } + // 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; - } + 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; - } - } - } + 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"); - } + // 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]; + host = value[start..pos]; - if (endPos == value.length) - { - return; - } + if (endPos == value.length) + { + return; + } - start = endPos; + 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; - } + 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..$]; - } - } + 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; - } -} + ~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; + /** + * 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; - } + 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"); + auto u = URL("example.org"); + assert(u.path == "example.org"); - u = URL("relative/path"); - assert(u.path == "relative/path"); + 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); + // 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 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 + 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"); + // 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"); + 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]); + } + } + } }