Index: objfw.spec ================================================================== --- objfw.spec +++ objfw.spec @@ -322,11 +322,11 @@ %{_includedir}/ObjFW/OFString+XMLUnescaping.h %{_includedir}/ObjFW/OFString.h %{_includedir}/ObjFW/OFSystemInfo.h %{_includedir}/ObjFW/OFTCPSocket.h %{_includedir}/ObjFW/OFTLSKey.h -%{_includedir}/ObjFW/OFTLSSocket.h +%{_includedir}/ObjFW/OFTLSStream.h %{_includedir}/ObjFW/OFTarArchive.h %{_includedir}/ObjFW/OFTarArchiveEntry.h %{_includedir}/ObjFW/OFThread.h %{_includedir}/ObjFW/OFThreadJoinFailedException.h %{_includedir}/ObjFW/OFThreadPool.h Index: src/Makefile ================================================================== --- src/Makefile +++ src/Makefile @@ -137,11 +137,11 @@ OFHTTPServer.m \ OFSequencedPacketSocket.m \ OFSocket.m \ OFStreamSocket.m \ OFTCPSocket.m \ - OFTLSSocket.m \ + OFTLSStream.m \ OFUDPSocket.m \ ${USE_SRCS_IPX} \ ${USE_SRCS_UNIX_SOCKETS} SRCS_IPX = OFIPXSocket.m \ OFSPXSocket.m \ Index: src/OFHTTPClient.h ================================================================== --- src/OFHTTPClient.h +++ src/OFHTTPClient.h @@ -25,10 +25,11 @@ @class OFHTTPClient; @class OFHTTPRequest; @class OFHTTPResponse; @class OFStream; @class OFTCPSocket; +@class OFTLSStream; @class OFURL; /** * @protocol OFHTTPClientDelegate OFHTTPClient.h ObjFW/OFHTTPClient.h * @@ -48,24 +49,36 @@ response: (nullable OFHTTPResponse *)response exception: (nullable id)exception; @optional /** - * @brief A callback which is called when an OFHTTPClient creates a socket. - * - * This is useful if the connection is using HTTPS and the server requires a - * client certificate. This callback can then be used to tell the TLS socket - * about the certificate. Another use case is to tell the socket about a SOCKS5 - * proxy it should use for this connection. - * - * @param client The OFHTTPClient that created a socket - * @param socket The socket created by the OFHTTPClient - * @param request The request for which the socket was created - */ -- (void)client: (OFHTTPClient *)client - didCreateSocket: (OFTCPSocket *)socket - request: (OFHTTPRequest *)request; + * @brief A callback which is called when an OFHTTPClient creates a TCP socket. + * + * This can be used to tell the socket about a SOCKS5 proxy it should use for + * this connection. + * + * @param client The OFHTTPClient that created a TCP socket + * @param TCPSocket The socket created by the OFHTTPClient + * @param request The request for which the TCP socket was created + */ +- (void)client: (OFHTTPClient *)client + didCreateTCPSocket: (OFTCPSocket *)TCPSocket + request: (OFHTTPRequest *)request; + +/** + * @brief A callback which is called when an OFHTTPClient creates a TLS stream. + * + * This can be used to tell the TLS stream about a client certificate it should + * use before performing the TLS handshake. + * + * @param client The OFHTTPClient that created a TLS stream + * @param TLSStream The TLS stream created by the OFHTTPClient + * @param request The request for which the TLS stream was created + */ +- (void)client: (OFHTTPClient *)client + didCreateTLSStream: (OFTLSStream *)TLSStream + request: (OFHTTPRequest *)request; /** * @brief A callback which is called when an OFHTTPClient wants to send the * body for a request. * @@ -134,11 +147,11 @@ #ifdef OF_HTTPCLIENT_M @public #endif OFObject *_Nullable _delegate; bool _allowsInsecureRedirects, _inProgress; - OFTCPSocket *_Nullable _socket; + OFStream *_Nullable _stream; OFURL *_Nullable _lastURL; bool _lastWasHEAD; OFHTTPResponse *_Nullable _lastResponse; } Index: src/OFHTTPClient.m ================================================================== --- src/OFHTTPClient.m +++ src/OFHTTPClient.m @@ -26,14 +26,13 @@ #import "OFHTTPRequest.h" #import "OFHTTPResponse.h" #import "OFKernelEventObserver.h" #import "OFNumber.h" #import "OFRunLoop.h" -#import "OFSocket+Private.h" #import "OFString.h" #import "OFTCPSocket.h" -#import "OFTLSSocket.h" +#import "OFTLSStream.h" #import "OFURL.h" #import "OFAlreadyConnectedException.h" #import "OFHTTPRequestFailedException.h" #import "OFInvalidArgumentException.h" @@ -50,11 +49,12 @@ #import "OFWriteFailedException.h" static const unsigned int defaultRedirects = 10; OF_DIRECT_MEMBERS -@interface OFHTTPClientRequestHandler: OFObject +@interface OFHTTPClientRequestHandler: OFObject { @public OFHTTPClient *_client; OFHTTPRequest *_request; unsigned int _redirects; @@ -73,32 +73,32 @@ OF_DIRECT_MEMBERS @interface OFHTTPClientRequestBodyStream: OFStream { OFHTTPClientRequestHandler *_handler; - OFTCPSocket *_socket; + OFStream *_stream; bool _chunked; unsigned long long _toWrite; bool _atEndOfStream; } - (instancetype)initWithHandler: (OFHTTPClientRequestHandler *)handler - socket: (OFTCPSocket *)sock; + stream: (OFStream *)stream; @end OF_DIRECT_MEMBERS @interface OFHTTPClientResponse: OFHTTPResponse { - OFTCPSocket *_socket; + OFStream *_stream; bool _hasContentLength, _chunked, _keepAlive; bool _atEndOfStream, _setAtEndOfStream; long long _toRead; } @property (nonatomic, setter=of_setKeepAlive:) bool of_keepAlive; -- (instancetype)initWithSocket: (OFTCPSocket *)sock; +- (instancetype)initWithStream: (OFStream *)stream; @end OF_DIRECT_MEMBERS @interface OFHTTPClientSyncPerformer: OFObject { @@ -294,20 +294,20 @@ didPerformRequest: _request response: nil exception: exception]; } -- (void)createResponseWithSocketOrThrow: (OFTCPSocket *)sock +- (void)createResponseWithStreamOrThrow: (OFStream *)stream { OFURL *URL = _request.URL; OFHTTPClientResponse *response; OFString *connectionHeader; bool keepAlive; OFString *location; id exception; - response = [[[OFHTTPClientResponse alloc] initWithSocket: sock] + response = [[[OFHTTPClientResponse alloc] initWithStream: stream] autorelease]; response.protocolVersionString = _version; response.statusCode = _status; response.headers = _serverHeaders; @@ -326,11 +326,11 @@ } if (keepAlive) { response.of_keepAlive = true; - _client->_socket = [sock retain]; + _client->_stream = [stream retain]; _client->_lastURL = [URL copy]; _client->_lastWasHEAD = (_request.method == OFHTTPRequestMethodHead); _client->_lastResponse = [response retain]; } @@ -430,14 +430,14 @@ withObject: response withObject: exception afterDelay: 0]; } -- (void)createResponseWithSocket: (OFTCPSocket *)sock +- (void)createResponseWithStream: (OFStream *)stream { @try { - [self createResponseWithSocketOrThrow: sock]; + [self createResponseWithStreamOrThrow: stream]; } @catch (id e) { [self raiseException: e]; } } @@ -472,11 +472,11 @@ _status = (short)status; return true; } -- (bool)handleServerHeader: (OFString *)line socket: (OFTCPSocket *)sock +- (bool)handleServerHeader: (OFString *)line stream: (OFStream *)stream { OFString *key, *value, *old; const char *lineC, *tmp; char *keyC; @@ -491,14 +491,14 @@ [_client->_delegate client: _client didReceiveHeaders: _serverHeaders statusCode: _status request: _request]; - sock.delegate = nil; + stream.delegate = nil; - [self performSelector: @selector(createResponseWithSocket:) - withObject: sock + [self performSelector: @selector(createResponseWithStream:) + withObject: stream afterDelay: 0]; return false; } @@ -533,11 +533,11 @@ [_serverHeaders setObject: value forKey: key]; return true; } -- (bool)stream: (OFStream *)sock +- (bool)stream: (OFStream *)stream didReadLine: (OFString *)line exception: (id)exception { bool ret; @@ -553,12 +553,11 @@ @try { if (_firstLine) { _firstLine = false; ret = [self handleFirstLine: line]; } else - ret = [self handleServerHeader: line - socket: (OFTCPSocket *)sock]; + ret = [self handleServerHeader: line stream: stream]; } @catch (id e) { [self raiseException: e]; ret = false; } @@ -595,12 +594,11 @@ if (chunked || [headers objectForKey: @"Content-Length"] != nil) { stream.delegate = nil; OFStream *requestBody = [[[OFHTTPClientRequestBodyStream alloc] - initWithHandler: self - socket: (OFTCPSocket *)stream] autorelease]; + initWithHandler: self stream: stream] autorelease]; if ([_client->_delegate respondsToSelector: @selector(client:wantsRequestBody:request:)]) [_client->_delegate client: _client wantsRequestBody: requestBody @@ -609,23 +607,23 @@ [stream asyncReadLine]; return nil; } -- (void)handleSocket: (OFTCPSocket *)sock +- (void)handleStream: (OFStream *)stream { /* * As a work around for a bug with split packets in lighttpd when using * HTTPS, we construct the complete request in a buffer string and then * send it all at once. * - * We do not use the socket's write buffer in case we need to resend + * We do not use the streams's write buffer in case we need to resend * the entire request (e.g. in case a keep-alive connection timed out). */ @try { - [sock asyncWriteString: constructRequestString(_request)]; + [stream asyncWriteString: constructRequestString(_request)]; } @catch (id e) { [self raiseException: e]; return; } } @@ -633,58 +631,93 @@ - (void)socket: (OFTCPSocket *)sock didConnectToHost: (OFString *)host port: (uint16_t)port exception: (id)exception { - sock.delegate = self; - if (exception != nil) { [self raiseException: exception]; return; } if ([_client->_delegate respondsToSelector: - @selector(client:didCreateSocket:request:)]) + @selector(client:didCreateTCPSocket:request:)]) [_client->_delegate client: _client - didCreateSocket: sock + didCreateTCPSocket: sock request: _request]; - [self performSelector: @selector(handleSocket:) - withObject: sock + if ([_request.URL.scheme caseInsensitiveCompare: @"https"] == + OFOrderedSame) { + OFTLSStream *stream; + @try { + stream = [OFTLSStream streamWithStream: sock]; + } @catch (OFNotImplementedException *e) { + [self raiseException: + [OFUnsupportedProtocolException + exceptionWithURL: _request.URL]]; + return; + } + + if ([_client->_delegate respondsToSelector: + @selector(client:didCreateTLSStream:request:)]) + [_client->_delegate client: _client + didCreateTLSStream: stream + request: _request]; + + stream.delegate = self; + [stream asyncPerformClientHandshakeWithHost: _request.URL.host]; + } else { + sock.delegate = self; + [self performSelector: @selector(handleStream:) + withObject: sock + afterDelay: 0]; + } +} + +- (void)stream: (OFTLSStream *)stream + didPerformClientHandshakeWithHost: (OFString *)host + exception: (id)exception +{ + if (exception != nil) { + [self raiseException: exception]; + return; + } + + [self performSelector: @selector(handleStream:) + withObject: stream afterDelay: 0]; } - (void)start { OFURL *URL = _request.URL; - OFTCPSocket *sock; + OFStream *stream; /* Can we reuse the last socket? */ - if (_client->_socket != nil && !_client->_socket.atEndOfStream && + if (_client->_stream != nil && !_client->_stream.atEndOfStream && [_client->_lastURL.scheme isEqual: URL.scheme] && [_client->_lastURL.host isEqual: URL.host] && (_client->_lastURL.port == URL.port || [_client->_lastURL.port isEqual: URL.port]) && (_client->_lastWasHEAD || _client->_lastResponse.atEndOfStream)) { /* - * Set _socket to nil, so that in case of an error it won't be - * reused. If everything is successful, we set _socket again + * Set _stream to nil, so that in case of an error it won't be + * reused. If everything is successful, we set _stream again * at the end. */ - sock = [_client->_socket autorelease]; - _client->_socket = nil; + stream = [_client->_stream autorelease]; + _client->_stream = nil; [_client->_lastURL release]; _client->_lastURL = nil; [_client->_lastResponse release]; _client->_lastResponse = nil; - sock.delegate = self; + stream.delegate = self; - [self performSelector: @selector(handleSocket:) - withObject: sock + [self performSelector: @selector(handleStream:) + withObject: stream afterDelay: 0]; } else [self closeAndReconnect]; } @@ -695,24 +728,18 @@ OFTCPSocket *sock; uint16_t port; OFNumber *URLPort; [_client close]; + + sock = [OFTCPSocket socket]; if ([URL.scheme caseInsensitiveCompare: @"https"] == - OFOrderedSame) { - @try { - sock = [OFTLSSocket socket]; - port = 443; - } @catch (OFNotImplementedException *e) { - @throw [OFUnsupportedProtocolException - exceptionWithURL: URL]; - } - } else { - sock = [OFTCPSocket socket]; + OFOrderedSame) + port = 443; + else port = 80; - } URLPort = URL.port; if (URLPort != nil) port = URLPort.unsignedShortValue; @@ -724,20 +751,20 @@ } @end @implementation OFHTTPClientRequestBodyStream - (instancetype)initWithHandler: (OFHTTPClientRequestHandler *)handler - socket: (OFTCPSocket *)sock + stream: (OFStream *)stream { self = [super init]; @try { OFDictionary OF_GENERIC(OFString *, OFString *) *headers; OFString *transferEncoding, *contentLengthString; _handler = [handler retain]; - _socket = [sock retain]; + _stream = [stream retain]; headers = _handler->_request.headers; transferEncoding = [headers objectForKey: @"Transfer-Encoding"]; _chunked = [transferEncoding isEqual: @"chunked"]; @@ -759,11 +786,11 @@ return self; } - (void)dealloc { - if (_socket != nil) + if (_stream != nil) [self close]; [_handler release]; [super dealloc]; @@ -771,11 +798,11 @@ - (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length { /* TODO: Use non-blocking writes */ - if (_socket == nil) + if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; /* * We must not send a chunk of size 0, as that would end the body. We * always ignore writing 0 bytes to still allow writing 0 bytes after @@ -789,17 +816,17 @@ requestedLength: length bytesWritten: 0 errNo: ENOTCONN]; if (_chunked) - [_socket writeFormat: @"%zX\r\n", length]; + [_stream writeFormat: @"%zX\r\n", length]; else if (length > _toWrite) @throw [OFOutOfRangeException exception]; - [_socket writeBuffer: buffer length: length]; + [_stream writeBuffer: buffer length: length]; if (_chunked) - [_socket writeString: @"\r\n"]; + [_stream writeString: @"\r\n"]; if (!_chunked) { _toWrite -= length; if (_toWrite == 0) @@ -814,48 +841,49 @@ return _atEndOfStream; } - (void)close { - if (_socket == nil) + if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; if (_chunked) - [_socket writeString: @"0\r\n\r\n"]; + [_stream writeString: @"0\r\n\r\n"]; else if (_toWrite > 0) @throw [OFTruncatedDataException exception]; - _socket.delegate = _handler; - [_socket asyncReadLine]; + _stream.delegate = _handler; + [_stream asyncReadLine]; - [_socket release]; - _socket = nil; + [_stream release]; + _stream = nil; [super close]; } - (int)fileDescriptorForWriting { - return _socket.fileDescriptorForWriting; + return ((OFStream *)_stream) + .fileDescriptorForWriting; } @end @implementation OFHTTPClientResponse @synthesize of_keepAlive = _keepAlive; -- (instancetype)initWithSocket: (OFTCPSocket *)sock +- (instancetype)initWithStream: (OFStream *)stream { self = [super init]; - _socket = [sock retain]; + _stream = [stream retain]; return self; } - (void)dealloc { - if (_socket != nil) + if (_stream != nil) [self close]; [super dealloc]; } @@ -889,30 +917,30 @@ } } - (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length { - if (_socket == nil) + if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; if (_atEndOfStream) return 0; if (!_hasContentLength && !_chunked) - return [_socket readIntoBuffer: buffer length: length]; + return [_stream readIntoBuffer: buffer length: length]; - if (_socket.atEndOfStream) + if (_stream.atEndOfStream) @throw [OFTruncatedDataException exception]; /* Content-Length */ if (!_chunked) { size_t ret; if (length > (unsigned long long)_toRead) length = (size_t)_toRead; - ret = [_socket readIntoBuffer: buffer length: length]; + ret = [_stream readIntoBuffer: buffer length: length]; if (ret > length) @throw [OFOutOfRangeException exception]; _toRead -= ret; @@ -924,11 +952,11 @@ /* Chunked */ if (_toRead == -2) { char tmp[2]; - switch ([_socket readIntoBuffer: tmp length: 2]) { + switch ([_stream readIntoBuffer: tmp length: 2]) { case 2: _toRead++; if (tmp[1] != '\n') @throw [OFInvalidServerReplyException exception]; @@ -944,11 +972,11 @@ return 0; } else if (_toRead == -1) { char tmp; - if ([_socket readIntoBuffer: &tmp length: 1] == 1) { + if ([_stream readIntoBuffer: &tmp length: 1] == 1) { _toRead++; if (tmp != '\n') @throw [OFInvalidServerReplyException exception]; } @@ -959,11 +987,11 @@ return 0; } else if (_toRead > 0) { if (length > (unsigned long long)_toRead) length = (size_t)_toRead; - length = [_socket readIntoBuffer: buffer length: length]; + length = [_stream readIntoBuffer: buffer length: length]; _toRead -= length; if (_toRead == 0) _toRead = -2; @@ -972,11 +1000,11 @@ void *pool = objc_autoreleasePoolPush(); OFString *line; size_t pos; @try { - line = [_socket tryReadLine]; + line = [_stream tryReadLine]; } @catch (OFInvalidEncodingException *e) { @throw [OFInvalidServerReplyException exception]; } if (line == nil) @@ -986,14 +1014,14 @@ if (pos != OFNotFound) line = [line substringToIndex: pos]; if (line.length < 1) { /* - * We have read the empty string because the socket is + * We have read the empty string because the stream is * at end of stream. */ - if (_socket.atEndOfStream && pos == OFNotFound) + if (_stream.atEndOfStream && pos == OFNotFound) @throw [OFTruncatedDataException exception]; else @throw [OFInvalidServerReplyException exception]; } @@ -1024,41 +1052,42 @@ - (bool)lowlevelIsAtEndOfStream { if (_atEndOfStream) return true; - if (_socket == nil) + if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; if (!_hasContentLength && !_chunked) - return _socket.atEndOfStream; + return _stream.atEndOfStream; return _atEndOfStream; } - (int)fileDescriptorForReading { - if (_socket == nil) + if (_stream == nil) return -1; - return _socket.fileDescriptorForReading; + return ((OFStream *)_stream) + .fileDescriptorForReading; } - (bool)hasDataInReadBuffer { - return (super.hasDataInReadBuffer || _socket.hasDataInReadBuffer); + return (super.hasDataInReadBuffer || _stream.hasDataInReadBuffer); } - (void)close { - if (_socket == nil) + if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; _atEndOfStream = false; - [_socket release]; - _socket = nil; + [_stream release]; + _stream = nil; [super close]; } @end @@ -1121,19 +1150,30 @@ didPerformRequest: request response: response exception: nil]; } -- (void)client: (OFHTTPClient *)client - didCreateSocket: (OFTCPSocket *)sock - request: (OFHTTPRequest *)request +- (void)client: (OFHTTPClient *)client + didCreateTCPSocket: (OFTCPSocket *)TCPSocket + request: (OFHTTPRequest *)request +{ + if ([_delegate respondsToSelector: + @selector(client:didCreateTCPSocket:request:)]) + [_delegate client: client + didCreateTCPSocket: TCPSocket + request: request]; +} + +- (void)client: (OFHTTPClient *)client + didCreateTLSStream: (OFTLSStream *)TLSStream + request: (OFHTTPRequest *)request { if ([_delegate respondsToSelector: - @selector(client:didCreateSocket:request:)]) - [_delegate client: client - didCreateSocket: sock - request: request]; + @selector(client:didCreateTLSStream:request:)]) + [_delegate client: client + didCreateTLSStream: TLSStream + request: request]; } - (void)client: (OFHTTPClient *)client wantsRequestBody: (OFStream *)body request: (OFHTTPRequest *)request @@ -1244,15 +1284,15 @@ objc_autoreleasePoolPop(pool); } - (void)close { - [_socket release]; - _socket = nil; + [_stream release]; + _stream = nil; [_lastURL release]; _lastURL = nil; [_lastResponse release]; _lastResponse = nil; } @end Index: src/OFHTTPServer.m ================================================================== --- src/OFHTTPServer.m +++ src/OFHTTPServer.m @@ -27,11 +27,10 @@ #import "OFHTTPRequest.h" #import "OFHTTPResponse.h" #import "OFNumber.h" #import "OFSocket+Private.h" #import "OFTCPSocket.h" -#import "OFTLSSocket.h" #import "OFThread.h" #import "OFTimer.h" #import "OFURL.h" #import "OFAlreadyConnectedException.h" Index: src/OFTCPSocket.m ================================================================== --- src/OFTCPSocket.m +++ src/OFTCPSocket.m @@ -51,12 +51,10 @@ #import "OFSetOptionFailedException.h" static const OFRunLoopMode connectRunLoopMode = @"OFTCPSocketConnectRunLoopMode"; -Class OFTLSSocketClass = Nil; - static OFString *defaultSOCKS5Host = nil; static uint16_t defaultSOCKS5Port = 1080; @interface OFTCPSocket () @end @@ -187,26 +185,26 @@ id delegate = _delegate; OFTCPSocketConnectDelegate *connectDelegate = [[[OFTCPSocketConnectDelegate alloc] init] autorelease]; OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; - self.delegate = connectDelegate; + _delegate = connectDelegate; [self asyncConnectToHost: host port: port runLoopMode: connectRunLoopMode]; while (!connectDelegate->_done) [runLoop runMode: connectRunLoopMode beforeDate: nil]; /* Cleanup */ [runLoop runMode: connectRunLoopMode beforeDate: [OFDate date]]; + + _delegate = delegate; if (connectDelegate->_exception != nil) @throw connectDelegate->_exception; - self.delegate = delegate; - objc_autoreleasePoolPop(pool); } - (void)asyncConnectToHost: (OFString *)host port: (uint16_t)port { Index: src/OFTCPSocketSOCKS5Connector.h ================================================================== --- src/OFTCPSocketSOCKS5Connector.h +++ src/OFTCPSocketSOCKS5Connector.h @@ -20,11 +20,10 @@ @class OFString; @interface OFTCPSocketSOCKS5Connector: OFObject { OFTCPSocket *_socket; - Class _socketClass; OFString *_host; uint16_t _port; id _Nullable _delegate; #ifdef OF_HAVE_BLOCKS OFTCPSocketAsyncConnectBlock _Nullable _block; Index: src/OFTCPSocketSOCKS5Connector.m ================================================================== --- src/OFTCPSocketSOCKS5Connector.m +++ src/OFTCPSocketSOCKS5Connector.m @@ -52,18 +52,10 @@ _delegate = [delegate retain]; #ifdef OF_HAVE_BLOCKS _block = [block copy]; #endif - /* - * Temporarily swizzle it to a TCP socket, so that if it's a - * TLS socket, we still get to talk to the SOCKS5 proxy - * directly. - */ - _socketClass = object_getClass(_socket); - object_setClass(_socket, [OFTCPSocket class]); - _socket.delegate = self; } @catch (id e) { [self release]; @throw e; } @@ -74,12 +66,10 @@ - (void)dealloc { if (_socket.delegate == self) _socket.delegate = _delegate; - object_setClass(_socket, _socketClass); - [_socket release]; [_host release]; [_delegate release]; #ifdef OF_HAVE_BLOCKS [_block release]; @@ -91,11 +81,10 @@ } - (void)didConnect { _socket.delegate = _delegate; - object_setClass(_socket, _socketClass); #ifdef OF_HAVE_BLOCKS if (_block != NULL) _block(_exception); else { DELETED src/OFTLSSocket.h Index: src/OFTLSSocket.h ================================================================== --- src/OFTLSSocket.h +++ src/OFTLSSocket.h @@ -1,119 +0,0 @@ -/* - * Copyright (c) 2008-2021 Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#import "OFTCPSocket.h" - -OF_ASSUME_NONNULL_BEGIN - -/** - * @protocol OFTLSSocketDelegate OFTLSSocket.h ObjFW/OFTLSSocket.h - * - * A delegate for OFTLSSocket. - */ -@protocol OFTLSSocketDelegate -@end - -/** - * @class OFTLSSocket OFTLSSocket.h ObjFW/OFTLSSocket.h - * - * @brief A class that provides Transport Layer Security on top of a TCP socket. - * - * This class is a class cluster and returns a suitable OFTLSSocket subclass, - * if available. - * - * Subclasses need to override @ref accept, @ref lowlevelReadIntoBuffer:length:, - * @ref lowlevelWriteBuffer:length: and @ref startTLSForHost:port:. The method - * @ref hasDataInReadBuffer should be overridden to return `true` if the TLS - * socket has cached unprocessed data internally, while returning - * `[super hasDataInReadBuffer]` if it does not have any unprocessed data. In - * order to get access to the lowlevel TCP methods (you cannot call `super`, as - * the class is abstract), the private methods @ref TCPAccept, - * @ref lowlevelTCPReadIntoBuffer:length: and - * @ref lowlevelTCPWriteBuffer:length: are provided. - */ -@interface OFTLSSocket: OFTCPSocket -{ - bool _verifiesCertificates; - OF_RESERVE_IVARS(OFTLSSocket, 4) -} - -/** - * @brief The delegate for asynchronous operations on the socket. - * - * @note The delegate is retained for as long as asynchronous operations are - * still ongoing. - */ -@property OF_NULLABLE_PROPERTY (assign, nonatomic) - id delegate; - -/** - * @brief Whether certificates are verified. - * - * The default is enabled. - */ -@property (nonatomic) bool verifiesCertificates; - -/** - * @brief Initializes the TLS socket with the specified TCP socket as its - * underlying socket. - * - * The passed socket will become invalid, as the internal socket handle gets - * moved from the specified socket to the OFTLSSocket. - * - * @param socket The TCP socket to use as underlying socket - */ -- (instancetype)initWithSocket: (OFTCPSocket *)socket; - -/** - * @brief Start TLS on the underlying socket with the assumption that it is - * connected to the specified host and port. - * - * @param host The host the socket is connected to, which is also used for - * verification - * @param port The port the socket is connected to - */ -- (void)startTLSForHost: (OFString *)host port: (uint16_t)port; - -/** - * @brief This method should never be called directly. Only subclasses of - * @ref OFTLSSocket are allowed to call it. - */ -- (instancetype)TCPAccept; - -/** - * @brief This method should never be called directly. Only subclasses of - * @ref OFTLSSocket are allowed to call it. - */ -- (size_t)lowlevelTCPReadIntoBuffer: (void *)buffer length: (size_t)length; - -/** - * @brief This method should never be called directly. Only subclasses of - * @ref OFTLSSocket are allowed to call it. - */ -- (size_t)lowlevelTCPWriteBuffer: (const void *)buffer length: (size_t)length; -@end - -#ifdef __cplusplus -extern "C" { -#endif -/** - * @brief The concrete subclass of OFTLSSocket that should be used. - */ -extern Class _Nullable OFTLSSocketImplementation; -#ifdef __cplusplus -} -#endif - -OF_ASSUME_NONNULL_END DELETED src/OFTLSSocket.m Index: src/OFTLSSocket.m ================================================================== --- src/OFTLSSocket.m +++ src/OFTLSSocket.m @@ -1,225 +0,0 @@ -/* - * Copyright (c) 2008-2021 Jonathan Schleifer - * - * All rights reserved. - * - * This file is part of ObjFW. It may be distributed under the terms of the - * Q Public License 1.0, which can be found in the file LICENSE.QPL included in - * the packaging of this file. - * - * Alternatively, it may be distributed under the terms of the GNU General - * Public License, either version 2 or 3, which can be found in the file - * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this - * file. - */ - -#include "config.h" - -#import "OFTLSSocket.h" -#import "OFSocket.h" -#import "OFSocket+Private.h" - -#import "OFInitializationFailedException.h" -#import "OFInvalidArgumentException.h" -#import "OFNotImplementedException.h" - -Class OFTLSSocketImplementation = Nil; - -@interface OFTLSSocketAsyncConnector: OFObject -{ - OFTLSSocket *_socket; - OFString *_host; - uint16_t _port; - id _delegate; -} - -- (instancetype)initWithSocket: (OFTLSSocket *)sock - host: (OFString *)host - port: (uint16_t)port - delegate: (id )delegate; -@end - -@implementation OFTLSSocketAsyncConnector -- (instancetype)initWithSocket: (OFTLSSocket *)sock - host: (OFString *)host - port: (uint16_t)port - delegate: (id )delegate -{ - self = [super init]; - - @try { - _socket = [sock retain]; - _host = [host copy]; - _port = port; - _delegate = [delegate retain]; - - _socket.delegate = self; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)dealloc -{ - if (_socket.delegate == self) - _socket.delegate = _delegate; - - [_socket release]; - [_delegate release]; - - [super dealloc]; -} - -- (void)socket: (OFTCPSocket *)sock - didConnectToHost: (OFString *)host - port: (uint16_t)port - exception: (id)exception -{ - if (exception == nil) { - @try { - [(OFTLSSocket *)sock startTLSForHost: _host - port: _port]; - } @catch (id e) { - [self release]; - @throw e; - } - } - - _socket.delegate = _delegate; - [_delegate socket: sock - didConnectToHost: host - port: port - exception: exception]; -} -@end - -@implementation OFTLSSocket -@dynamic delegate; -@synthesize verifiesCertificates = _verifiesCertificates; - -+ (instancetype)alloc -{ - if (self == [OFTLSSocket class]) { - if (OFTLSSocketImplementation == nil) - @throw [OFNotImplementedException - exceptionWithSelector: _cmd - object: self]; - - return [OFTLSSocketImplementation alloc]; - } - - return [super alloc]; -} - -- (instancetype)init -{ - self = [super init]; - - _verifiesCertificates = true; - - return self; -} - -- (instancetype)initWithSocket: (OFTCPSocket *)socket -{ - self = [super init]; - - @try { - if ([socket isKindOfClass: [OFTLSSocket class]]) - @throw [OFInvalidArgumentException exception]; - - _socket = socket->_socket; - socket->_socket = OFInvalidSocketHandle; - - _verifiesCertificates = true; - } @catch (id e) { - [self release]; - @throw e; - } - - return self; -} - -- (void)startTLSForHost: (OFString *)host port: (uint16_t)port -{ - OF_UNRECOGNIZED_SELECTOR -} - -- (void)asyncConnectToHost: (OFString *)host - port: (uint16_t)port - runLoopMode: (OFRunLoopMode)runLoopMode -{ - void *pool = objc_autoreleasePoolPush(); - - [[[OFTLSSocketAsyncConnector alloc] - initWithSocket: self - host: host - port: port - delegate: _delegate] autorelease]; - [super asyncConnectToHost: host port: port runLoopMode: runLoopMode]; - - objc_autoreleasePoolPop(pool); -} - -#ifdef OF_HAVE_BLOCKS -- (void)asyncConnectToHost: (OFString *)host - port: (uint16_t)port - runLoopMode: (OFRunLoopMode)runLoopMode - block: (OFTCPSocketAsyncConnectBlock)block -{ - [super asyncConnectToHost: host - port: port - runLoopMode: runLoopMode - block: ^ (id exception) { - if (exception == nil) { - @try { - [self startTLSForHost: host port: port]; - } @catch (id e) { - block(e); - return; - } - } - - block(exception); - }]; -} -#endif - -- (instancetype)accept -{ - OF_UNRECOGNIZED_SELECTOR -} - -- (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length -{ - OF_UNRECOGNIZED_SELECTOR -} - -- (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length -{ - OF_UNRECOGNIZED_SELECTOR -} - -- (instancetype)TCPAccept -{ - return [super accept]; -} - -- (size_t)lowlevelTCPReadIntoBuffer: (void *)buffer length: (size_t)length -{ - return [super lowlevelReadIntoBuffer: buffer length: length]; -} - -- (size_t)lowlevelTCPWriteBuffer: (const void *)buffer length: (size_t)length -{ - return [super lowlevelWriteBuffer: buffer length: length]; -} - -- (bool)lowlevelTCPIsAtEndOfStream -{ - return [super lowlevelIsAtEndOfStream]; -} -@end ADDED src/OFTLSStream.h Index: src/OFTLSStream.h ================================================================== --- src/OFTLSStream.h +++ src/OFTLSStream.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#import "OFStream.h" +#import "OFRunLoop.h" + +OF_ASSUME_NONNULL_BEGIN + +/** @file */ + +@class OFTLSStream; + +/** + * @protocol OFTLSStreamDelegate OFTLSStream.h ObjFW/OFTLSStream.h + * + * A delegate for OFTLSStream. + */ +@protocol OFTLSStreamDelegate +/** + * @brief A method which is called when a TLS stream performed the client + * handshake. + * + * @param stream The TLS stream which performed the handshake + * @param host The host for which the handshake was performed + * @param exception An exception that occurred during the handshake, or nil on + * success + */ +- (void)stream: (OFTLSStream *)stream + didPerformClientHandshakeWithHost: (OFString *)host + exception: (nullable id)exception; +@end + +/** + * @class OFTLSStream OFTLSStream.h ObjFW/OFTLSStream.h + * + * @brief A class that provides Transport Layer Security on top of a stream. + * + * This class is a class cluster and returns a suitable OFTLSStream subclass, + * if available. + * + * Subclasses need to override @ref lowlevelReadIntoBuffer:length:, + * @ref lowlevelWriteBuffer:length: and + * @ref asyncPerformClientHandshakeWithHost:runLoopMode:. The method + * @ref hasDataInReadBuffer should be overridden to return `true` if the TLS + * stream has cached unprocessed data internally, while returning + * `self.wrappedStream.hasDataInReadBuffer` if it does not have any unprocessed + * data. In order to get access to the wrapped stream, @ref wrappedStream can + * be used. + */ +@interface OFTLSStream: OFStream +{ + OFStream + *_wrappedStream; + bool _verifiesCertificates; + OF_RESERVE_IVARS(OFTLSStream, 4) +} + +/** + * @brief The wrapped stream. + */ +@property (readonly, nonatomic) OFStream *wrappedStream; + +/** + * @brief The delegate for asynchronous operations on the stream. + * + * @note The delegate is retained for as long as asynchronous operations are + * still ongoing. + */ +@property OF_NULLABLE_PROPERTY (assign, nonatomic) + id delegate; + +/** + * @brief Whether certificates are verified. Default is true. + */ +@property (nonatomic) bool verifiesCertificates; + +- (instancetype)init OF_UNAVAILABLE; + +/** + * @brief Creates a new TLS stream with the specified stream as its underlying + * stream. + * + * @param stream The stream to use as underlying stream. Must not be closed + * before the TLS stream is closed. + * @return A new, autoreleased TLS stream + */ ++ (instancetype)streamWithStream: (OFStream *)stream; + +/** + * @brief Initializes the TLS stream with the specified stream as its + * underlying stream. + * + * @param stream The stream to use as underlying stream. Must not be closed + * before the TLS stream is closed. + * @return An initialized TLS stream + */ +- (instancetype)initWithStream: (OFStream *)stream; + +/** + * @brief Asynchronously performs the TLS client handshake for the specified + * host and calls the delegate afterwards. + * + * @param host The host to perform the handshake with + */ +- (void)asyncPerformClientHandshakeWithHost: (OFString *)host; + +/** + * @brief Asynchronously performs the TLS client handshake for the specified + * host and calls the delegate afterwards. + * + * @param host The host to perform the handshake with + * @param runLoopMode The run loop mode in which to perform the async handshake + */ +- (void)asyncPerformClientHandshakeWithHost: (OFString *)host + runLoopMode: (OFRunLoopMode)runLoopMode; + +/** + * @brief Performs the TLS client handshake for the specified host. + * + * @param host The host to perform the handshake with + */ +- (void)performClientHandshakeWithHost: (OFString *)host; +@end + +#ifdef __cplusplus +extern "C" { +#endif +/** + * @brief The implementation for OFTLSStream to use. + * + * This can be set to a class that is always used for OFTLSStream. This is + * useful to either force a specific implementation or use one that ObjFW does + * not know about. + */ +extern Class OFTLSStreamImplementation; +#ifdef __cplusplus +} +#endif + +OF_ASSUME_NONNULL_END ADDED src/OFTLSStream.m Index: src/OFTLSStream.m ================================================================== --- src/OFTLSStream.m +++ src/OFTLSStream.m @@ -0,0 +1,172 @@ +/* + * Copyright (c) 2008-2021 Jonathan Schleifer + * + * All rights reserved. + * + * This file is part of ObjFW. It may be distributed under the terms of the + * Q Public License 1.0, which can be found in the file LICENSE.QPL included in + * the packaging of this file. + * + * Alternatively, it may be distributed under the terms of the GNU General + * Public License, either version 2 or 3, which can be found in the file + * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this + * file. + */ + +#include "config.h" + +#import "OFTLSStream.h" +#import "OFDate.h" + +#import "OFNotImplementedException.h" + +Class OFTLSStreamImplementation = Nil; +static const OFRunLoopMode handshakeRunLoopMode = + @"OFTLSStreamHandshakeRunLoopMode"; + +@interface OFTLSStreamHandshakeDelegate: OFObject +{ +@public + bool _done; + id _exception; +} +@end + +@implementation OFTLSStreamHandshakeDelegate +- (void)dealloc +{ + [_exception release]; + + [super dealloc]; +} + +- (void)stream: (OFTLSStream *)stream + didPerformClientHandshakeWithHost: (OFString *)host + exception: (id)exception +{ + _done = true; + _exception = [exception retain]; +} +@end + +@implementation OFTLSStream +@synthesize wrappedStream = _wrappedStream; +@dynamic delegate; +@synthesize verifiesCertificates = _verifiesCertificates; + ++ (instancetype)alloc +{ + if (self == [OFTLSStream class]) { + if (OFTLSStreamImplementation != Nil) + return [OFTLSStreamImplementation alloc]; + + @throw [OFNotImplementedException exceptionWithSelector: _cmd + object: self]; + } + + return [super alloc]; +} + ++ (instancetype)streamWithStream: (OFStream *)stream +{ + return [[[self alloc] initWithStream: stream] autorelease]; +} + +- (instancetype)init +{ + OF_INVALID_INIT_METHOD +} + +- (instancetype)initWithStream: (OFStream *)stream +{ + self = [super init]; + + @try { + _wrappedStream = [stream retain]; + _verifiesCertificates = true; + } @catch (id e) { + [self release]; + @throw e; + } + + return self; +} + +- (void)dealloc +{ + [_wrappedStream release]; + + [super dealloc]; +} + +- (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length +{ + OF_UNRECOGNIZED_SELECTOR +} + +- (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length +{ + OF_UNRECOGNIZED_SELECTOR +} + +- (bool)hasDataInReadBuffer +{ + return (super.hasDataInReadBuffer || + _wrappedStream.hasDataInReadBuffer); +} + +- (bool)lowlevelIsAtEndOfStream +{ + return _wrappedStream.atEndOfStream; +} + +- (int)fileDescriptorForReading +{ + return _wrappedStream.fileDescriptorForReading; +} + +- (int)fileDescriptorForWriting +{ + return _wrappedStream.fileDescriptorForWriting; +} + +- (void)asyncPerformClientHandshakeWithHost: (OFString *)host +{ + [self asyncPerformClientHandshakeWithHost: host + runLoopMode: OFDefaultRunLoopMode]; +} + +- (void)asyncPerformClientHandshakeWithHost: (OFString *)host + runLoopMode: (OFRunLoopMode)runLoopMode +{ + OF_UNRECOGNIZED_SELECTOR +} + +- (void)performClientHandshakeWithHost: (OFString *)host +{ + void *pool = objc_autoreleasePoolPush(); + id delegate = _delegate; + OFTLSStreamHandshakeDelegate *handshakeDelegate = + [[[OFTLSStreamHandshakeDelegate alloc] init] autorelease]; + OFRunLoop *runLoop = [OFRunLoop currentRunLoop]; + + _delegate = handshakeDelegate; + [self asyncPerformClientHandshakeWithHost: host + runLoopMode: handshakeRunLoopMode]; + + while (!handshakeDelegate->_done) + [runLoop runMode: handshakeRunLoopMode beforeDate: nil]; + + /* Cleanup */ + [runLoop runMode: handshakeRunLoopMode beforeDate: [OFDate date]]; + + _delegate = delegate; + + if (handshakeDelegate->_exception != nil) + @throw handshakeDelegate->_exception; + + objc_autoreleasePoolPop(pool); +} +@end Index: src/ObjFW.h ================================================================== --- src/ObjFW.h +++ src/ObjFW.h @@ -74,11 +74,11 @@ # import "OFStreamSocket.h" # import "OFDatagramSocket.h" # import "OFSequencedPacketSocket.h" # import "OFTCPSocket.h" # import "OFUDPSocket.h" -# import "OFTLSSocket.h" +# import "OFTLSStream.h" # import "OFKernelEventObserver.h" # import "OFDNSQuery.h" # import "OFDNSResourceRecord.h" # import "OFDNSResponse.h" # import "OFDNSResolver.h" Index: utils/ofhttp/OFHTTP.m ================================================================== --- utils/ofhttp/OFHTTP.m +++ utils/ofhttp/OFHTTP.m @@ -31,11 +31,11 @@ #endif #import "OFSandbox.h" #import "OFStdIOStream.h" #import "OFSystemInfo.h" #import "OFTCPSocket.h" -#import "OFTLSSocket.h" +#import "OFTLSStream.h" #import "OFURL.h" #import "OFConnectionFailedException.h" #import "OFHTTPRequestFailedException.h" #import "OFInvalidArgumentException.h" @@ -276,21 +276,10 @@ objc_autoreleasePoolPop(pool); return [fileName autorelease]; } @implementation OFHTTP -#ifdef OF_HAVE_PLUGINS -+ (void)initialize -{ - if (self != [OFHTTP class]) - return; - - /* Opportunistically try loading ObjOpenSSL and ignore any errors. */ - OFDLOpen(@LIB_PREFIX @"objopenssl" @LIB_SUFFIX, OFDLOpenFlagLazy); -} -#endif - - (instancetype)init { self = [super init]; @try { @@ -534,11 +523,11 @@ else [sandbox unveilPath: [[OFFileManager defaultManager] currentDirectoryPath] permissions: (_continue ? @"rwc" : @"wc")]; - /* In case we use ObjOpenSSL for https later */ + /* In case we use OpenSSL for HTTPS later */ [sandbox unveilPath: @"/etc/ssl" permissions: @"r"]; sandbox.allowsUnveil = false; [OFApplication of_activateSandbox: sandbox]; #endif @@ -581,16 +570,15 @@ _useUnicode = ([OFLocale encoding] == OFStringEncodingUTF8); [self performSelector: @selector(downloadNextURL) afterDelay: 0]; } -- (void)client: (OFHTTPClient *)client - didCreateSocket: (OFTCPSocket *)sock - request: (OFHTTPRequest *)request +- (void)client: (OFHTTPClient *)client + didCreateTLSStream: (OFTLSStream *)stream + request: (OFHTTPRequest *)request { - if (_insecure && [sock isKindOfClass: [OFTLSSocket class]]) - ((OFTLSSocket *)sock).verifiesCertificates = false; + stream.verifiesCertificates = !_insecure; } - (void)client: (OFHTTPClient *)client wantsRequestBody: (OFStream *)body request: (OFHTTPRequest *)request @@ -842,15 +830,16 @@ } else if ([exception isKindOfClass: [OFUnsupportedProtocolException class]]) { if (!_quiet) [OFStdOut writeString: @"\n"]; - [OFStdErr writeLine: OF_LOCALIZED(@"no_ssl_library", - @"%[prog]: No TLS library loaded!\n" - @" In order to download via https, you need to " - @"preload an TLS library for ObjFW\n" - @" such as ObjOpenSSL!", + [OFStdErr writeLine: OF_LOCALIZED(@"no_tls_support", + @"%[prog]: No TLS support in ObjFW!\n" + @" In order to download via HTTPS, you need to " + @"either build ObjFW with TLS\n" + @" support or preload a library adding TLS " + @"support to ObjFW!", @"prog", [OFApplication programName])]; } else if ([exception isKindOfClass: [OFReadOrWriteFailedException class]]) { OFString *error = OF_LOCALIZED( @"download_failed_read_or_write_failed_any", Index: utils/ofhttp/lang/de.json ================================================================== --- utils/ofhttp/lang/de.json +++ utils/ofhttp/lang/de.json @@ -50,15 +50,17 @@ ], "download_failed_invalid_server_reply": [ "%[prog]: Fehler beim Download von <%[url]>!\n", " Ungültige Antwort vom Server!" ], - "no_ssl_library": [ - "%[prog]: Keine TLS-Bibliothek geladen!\n", - " Um Dateien über https zu laden, müssen Sie eine TLS-Bibliothek für ", - "ObjFW,\n", - " wie z.B. ObjOpenSSL, mittels LD_PRELOAD laden." + "no_tls_support": [ + "%[prog]: Keine TLS-Unterstützung in ObjFW!\n", + " Um via HTTPS runterzuladen, müssen Sie entweder ObjFW mit TLS-", + "Unterstützung\n", + " kompilieren oder eine Bibliothek mittels „preoad” laden, welche ", + "TLS-Support\n", + " zu ObjFW hinzufügt!" ], "download_failed_read_or_write_failed_any": "Lesen oder Schreiben", "download_failed_read_or_write_failed_read": "Lesen", "download_failed_read_or_write_failed_write": "Schreiben", "download_failed_read_or_write_failed": [