ObjFW  Check-in [d30efa8bbf]

Overview
Comment:Completely rework the TLS/SSL API

The previous API could never work cleanly and would always require
hacks, as it needed intercepting all interactions of OFTCPSocket with
the raw socket and did not work at all if the OFTCPSocket had anything
in its read buffer before starting the TLS handshake. This also could
not be fixed easily, as it would have required the object to contain two
read buffers, one for the unencrypted connection and one for the
encrypted connection. There was also no clean way to perform the
handshake in a non-blocking way.

The new API is a lot cleaner and requires none of the hacks, but using
it requires slightly more work. But this is more than made up for by
making a fully asynchronous handshake possible. It uses the concept of a
stream wrapping another stream, meaning the entire connecting part is
being handled by OFTCPSocket and then the connected socket is passed off
to OFTLSStream to wrap it. This also makes for a lot cleaner separation
of concerns.

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: d30efa8bbf4eb040901e963da4fb8a5e0fcc7b500625b5da8a20dd388d42eacb
User & Date: js on 2021-11-13 13:04:13
Other Links: manifest | tags
Context
2021-11-13
20:28
Add OFSecureTransportTLSStream check-in: 112eb62f7b user: js tags: trunk
13:04
Completely rework the TLS/SSL API check-in: d30efa8bbf user: js tags: trunk
2021-11-07
20:02
Make SOCKS5 work with TLS sockets check-in: eb2bfc674b user: js tags: trunk
Changes

Modified objfw.spec from [c6704c25c0] to [cda61264f6].

320
321
322
323
324
325
326
327

328
329
330
331
332
333
334
320
321
322
323
324
325
326

327
328
329
330
331
332
333
334







-
+







%{_includedir}/ObjFW/OFString+URLEncoding.h
%{_includedir}/ObjFW/OFString+XMLEscaping.h
%{_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
%{_includedir}/ObjFW/OFThreadStartFailedException.h
%{_includedir}/ObjFW/OFThreadStillRunningException.h

Modified src/Makefile from [099a565a95] to [dc8f45eb96].

135
136
137
138
139
140
141
142

143
144
145
146
147
148
149
135
136
137
138
139
140
141

142
143
144
145
146
147
148
149







-
+







	       OFHTTPRequest.m			\
	       OFHTTPResponse.m			\
	       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	\
	   OFSPXStreamSocket.m
SRCS_UNIX_SOCKETS = OFUNIXDatagramSocket.m	\

Modified src/OFHTTPClient.h from [e81e6f28b9] to [dfdc56fd61].

23
24
25
26
27
28
29

30
31
32
33
34
35
36
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37







+








@class OFDictionary OF_GENERIC(KeyType, ObjectType);
@class OFHTTPClient;
@class OFHTTPRequest;
@class OFHTTPResponse;
@class OFStream;
@class OFTCPSocket;
@class OFTLSStream;
@class OFURL;

/**
 * @protocol OFHTTPClientDelegate OFHTTPClient.h ObjFW/OFHTTPClient.h
 *
 * @brief A delegate for OFHTTPClient.
 */
46
47
48
49
50
51
52
53

54
55
56
57
58


59
60
61
62



63
64
65
66

















67
68
69
70
71
72
73
47
48
49
50
51
52
53

54
55




56
57
58



59
60
61
62



63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86







-
+

-
-
-
-
+
+

-
-
-
+
+
+

-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+







-      (void)client: (OFHTTPClient *)client
  didPerformRequest: (OFHTTPRequest *)request
	   response: (nullable OFHTTPResponse *)response
	  exception: (nullable id)exception;

@optional
/**
 * @brief A callback which is called when an OFHTTPClient creates a socket.
 * @brief A callback which is called when an OFHTTPClient creates a TCP 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.
 * 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 socket
 * @param socket The socket created by the OFHTTPClient
 * @param request The request for which the socket was created
 * @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
  didCreateSocket: (OFTCPSocket *)socket
	  request: (OFHTTPRequest *)request;
-	(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.
 *
 * @param client The OFHTTPClient that wants to send the body
 * @param requestBody A stream into which the body of the request should be
132
133
134
135
136
137
138
139

140
141
142
143
144
145
146
145
146
147
148
149
150
151

152
153
154
155
156
157
158
159







-
+







@interface OFHTTPClient: OFObject
{
#ifdef OF_HTTPCLIENT_M
@public
#endif
	OFObject <OFHTTPClientDelegate> *_Nullable _delegate;
	bool _allowsInsecureRedirects, _inProgress;
	OFTCPSocket *_Nullable _socket;
	OFStream *_Nullable _stream;
	OFURL *_Nullable _lastURL;
	bool _lastWasHEAD;
	OFHTTPResponse *_Nullable _lastResponse;
}

/**
 * @brief The delegate of the HTTP request.

Modified src/OFHTTPClient.m from [63931bf44e] to [0ef7b9c0c4].

24
25
26
27
28
29
30
31
32
33
34

35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55


56
57
58
59
60
61
62
24
25
26
27
28
29
30

31
32

33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53

54
55
56
57
58
59
60
61
62







-


-
+




















-
+
+







#import "OFData.h"
#import "OFDictionary.h"
#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"
#import "OFInvalidEncodingException.h"
#import "OFInvalidFormatException.h"
#import "OFInvalidServerReplyException.h"
#import "OFNotImplementedException.h"
#import "OFNotOpenException.h"
#import "OFOutOfMemoryException.h"
#import "OFOutOfRangeException.h"
#import "OFTruncatedDataException.h"
#import "OFUnsupportedProtocolException.h"
#import "OFUnsupportedVersionException.h"
#import "OFWriteFailedException.h"

static const unsigned int defaultRedirects = 10;

OF_DIRECT_MEMBERS
@interface OFHTTPClientRequestHandler: OFObject <OFTCPSocketDelegate>
@interface OFHTTPClientRequestHandler: OFObject <OFTCPSocketDelegate,
    OFTLSStreamDelegate>
{
@public
	OFHTTPClient *_client;
	OFHTTPRequest *_request;
	unsigned int _redirects;
	bool _firstLine;
	OFString *_version;
71
72
73
74
75
76
77
78

79
80
81
82
83
84
85

86
87
88
89
90
91

92
93
94
95
96
97
98
99

100
101
102
103
104
105
106
71
72
73
74
75
76
77

78
79
80
81
82
83
84

85
86
87
88
89
90

91
92
93
94
95
96
97
98

99
100
101
102
103
104
105
106







-
+






-
+





-
+







-
+







- (void)closeAndReconnect;
@end

OF_DIRECT_MEMBERS
@interface OFHTTPClientRequestBodyStream: OFStream <OFReadyForWritingObserving>
{
	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 <OFReadyForReadingObserving>
{
	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 <OFHTTPClientDelegate>
{
	OFHTTPClient *_client;
	OFObject <OFHTTPClientDelegate> *_delegate;
292
293
294
295
296
297
298
299

300
301
302
303
304
305
306
307
308

309
310
311
312
313
314
315
292
293
294
295
296
297
298

299
300
301
302
303
304
305
306
307

308
309
310
311
312
313
314
315







-
+








-
+








	[_client->_delegate client: _client
		 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;

	connectionHeader = [_serverHeaders objectForKey: @"Connection"];
	if ([_version isEqual: @"1.1"]) {
324
325
326
327
328
329
330
331

332
333
334
335
336
337
338
324
325
326
327
328
329
330

331
332
333
334
335
336
337
338







-
+







		else
			keepAlive = false;
	}

	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];
	}

	if (_redirects > 0 && (_status == 301 || _status == 302 ||
428
429
430
431
432
433
434
435

436
437
438

439
440
441
442
443
444
445
428
429
430
431
432
433
434

435
436
437

438
439
440
441
442
443
444
445







-
+


-
+







				 withObject: _client
				 withObject: _request
				 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];
	}
}

- (bool)handleFirstLine: (OFString *)line
{
470
471
472
473
474
475
476
477

478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496

497
498
499


500
501
502
503
504
505
506
470
471
472
473
474
475
476

477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495

496
497


498
499
500
501
502
503
504
505
506







-
+


















-
+

-
-
+
+







		@throw [OFInvalidServerReplyException exception];

	_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;

	if (line == nil)
		@throw [OFInvalidServerReplyException exception];

	if (line.length == 0) {
		[_serverHeaders makeImmutable];

		if ([_client->_delegate respondsToSelector: @selector(client:
		    didReceiveHeaders:statusCode:request:)])
			[_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;
	}

	lineC = line.UTF8String;

531
532
533
534
535
536
537
538

539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558

559
560
561
562
563
564
565
566
531
532
533
534
535
536
537

538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557

558

559
560
561
562
563
564
565







-
+



















-
+
-







		value = [old stringByAppendingFormat: @",%@", value];

	[_serverHeaders setObject: value forKey: key];

	return true;
}

- (bool)stream: (OFStream *)sock
- (bool)stream: (OFStream *)stream
   didReadLine: (OFString *)line
     exception: (id)exception
{
	bool ret;

	if (exception != nil) {
		if ([exception isKindOfClass:
		    [OFInvalidEncodingException class]])
			exception = [OFInvalidServerReplyException exception];

		[self raiseException: exception];
		return false;
	}

	@try {
		if (_firstLine) {
			_firstLine = false;
			ret = [self handleFirstLine: line];
		} else
			ret = [self handleServerHeader: line
			ret = [self handleServerHeader: line stream: stream];
						socket: (OFTCPSocket *)sock];
	} @catch (id e) {
		[self raiseException: e];
		ret = false;
	}

	return ret;
}
593
594
595
596
597
598
599
600

601
602
603
604
605
606
607
608
609
610
611
612
613
614

615
616
617
618
619
620
621

622
623
624
625
626

627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646

647
648

649
650






















651
652

















653
654
655
656
657
658
659

660
661
662

663
664
665
666
667
668
669
670


671
672
673
674


675
676
677
678
679
680
681
682

683
684
685


686
687
688
689
690
691
692
693
694
695
696
697
698
699
700


701
702

703
704
705

706
707
708
709
710

711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729

730
731
732
733
734
735
736
737
738

739
740
741
742
743
744
745
592
593
594
595
596
597
598

599

600
601
602
603
604
605
606
607
608
609
610
611

612
613
614
615
616
617
618

619
620
621
622
623

624
625
626
627
628
629
630
631
632
633
634
635


636
637
638
639
640
641

642
643

644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668


669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691

692
693
694

695
696
697
698
699
700
701


702
703
704
705


706
707
708
709
710
711
712
713
714

715
716


717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736

737



738





739

740

741
742
743
744
745
746
747
748
749
750
751
752
753
754
755

756
757
758
759
760
761
762
763
764

765
766
767
768
769
770
771
772







-
+
-












-
+






-
+




-
+











-
-






-
+

-
+


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+






-
+


-
+






-
-
+
+


-
-
+
+







-
+

-
-
+
+















+
+

-
+
-
-
-
+
-
-
-
-
-
+
-

-















-
+








-
+







	chunked = [[headers objectForKey: @"Transfer-Encoding"]
	    isEqual: @"chunked"];

	if (chunked || [headers objectForKey: @"Content-Length"] != nil) {
		stream.delegate = nil;

		OFStream *requestBody = [[[OFHTTPClientRequestBodyStream alloc]
		    initWithHandler: self
		    initWithHandler: self stream: stream] autorelease];
			     socket: (OFTCPSocket *)stream] autorelease];

		if ([_client->_delegate respondsToSelector:
		    @selector(client:wantsRequestBody:request:)])
			[_client->_delegate client: _client
				  wantsRequestBody: requestBody
					   request: _request];
	} else
		[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;
	}
}

-     (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];

	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(handleSocket:)
		   withObject: sock
		[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];
}

- (void)closeAndReconnect
{
	@try {
		OFURL *URL = _request.URL;
		OFTCPSocket *sock;
		uint16_t port;
		OFNumber *URLPort;

		[_client close];

		sock = [OFTCPSocket socket];

		if ([URL.scheme caseInsensitiveCompare: @"https"] ==
		    OFOrderedSame) {
		    OFOrderedSame)
			@try {
				sock = [OFTLSSocket socket];
				port = 443;
			port = 443;
			} @catch (OFNotImplementedException *e) {
				@throw [OFUnsupportedProtocolException
				    exceptionWithURL: URL];
			}
		} else {
		else
			sock = [OFTCPSocket socket];
			port = 80;
		}

		URLPort = URL.port;
		if (URLPort != nil)
			port = URLPort.unsignedShortValue;

		sock.delegate = self;
		[sock asyncConnectToHost: URL.host port: port];
	} @catch (id e) {
		[self raiseException: e];
	}
}
@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"];

		contentLengthString = [headers objectForKey: @"Content-Length"];
757
758
759
760
761
762
763
764

765
766
767
768
769
770
771
772
773
774
775
776

777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794

795
796
797
798

799
800

801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819

820
821
822
823

824
825
826
827
828


829
830
831


832
833
834
835
836
837

838

839
840
841
842
843
844
845

846
847
848
849

850
851
852
853
854
855
856

857
858
859
860
861
862
863
784
785
786
787
788
789
790

791
792
793
794
795
796
797
798
799
800
801
802

803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820

821
822
823
824

825
826

827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845

846
847
848
849

850
851
852
853


854
855
856


857
858
859
860
861
862
863
864
865

866
867
868
869
870
871
872

873
874
875
876

877
878
879
880
881
882
883

884
885
886
887
888
889
890
891







-
+











-
+

















-
+



-
+

-
+


















-
+



-
+



-
-
+
+

-
-
+
+






+
-
+






-
+



-
+






-
+







	}

	return self;
}

- (void)dealloc
{
	if (_socket != nil)
	if (_stream != nil)
		[self close];

	[_handler release];

	[super dealloc];
}

- (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
	 * the end of stream.
	 */
	if (length == 0)
		return 0;

	if (_atEndOfStream)
		@throw [OFWriteFailedException exceptionWithObject: self
						   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)
			_atEndOfStream = true;
	}

	return length;
}

- (bool)lowlevelIsAtEndOfStream
{
	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 ((OFStream <OFReadyForWritingObserving> *)_stream)
	return _socket.fileDescriptorForWriting;
	    .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];
}

- (void)setHeaders: (OFDictionary *)headers
{
887
888
889
890
891
892
893
894

895
896
897
898
899
900
901

902
903

904
905
906
907
908
909
910
911
912
913

914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929

930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949

950
951
952
953
954
955
956
957
958
959
960
961
962
963
964

965
966
967
968
969
970
971
972
973
974
975
976
977

978
979
980
981
982
983
984
985
986
987
988
989
990
991

992
993
994

995
996
997
998
999
1000
1001
915
916
917
918
919
920
921

922
923
924
925
926
927
928

929
930

931
932
933
934
935
936
937
938
939
940

941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956

957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976

977
978
979
980
981
982
983
984
985
986
987
988
989
990
991

992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004

1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018

1019
1020
1021

1022
1023
1024
1025
1026
1027
1028
1029







-
+






-
+

-
+









-
+















-
+



















-
+














-
+












-
+













-
+


-
+







			@throw [OFInvalidServerReplyException exception];
		}
	}
}

- (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;

		if (_toRead == 0)
			_atEndOfStream = true;

		return ret;
	}

	/* 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];
		case 1:
			_toRead++;
			if (tmp[0] != '\r')
				@throw [OFInvalidServerReplyException
				    exception];
		}

		if (_setAtEndOfStream && _toRead == 0)
			_atEndOfStream = true;

		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];
		}

		if (_setAtEndOfStream && _toRead == 0)
			_atEndOfStream = true;

		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;

		return length;
	} else {
		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)
			return 0;

		pos = [line rangeOfString: @";"].location;
		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];
		}

		@try {
1022
1023
1024
1025
1026
1027
1028
1029

1030
1031
1032
1033

1034
1035
1036
1037
1038
1039
1040

1041
1042

1043

1044
1045
1046
1047
1048

1049
1050
1051
1052
1053

1054
1055
1056
1057
1058
1059


1060
1061
1062
1063
1064
1065
1066
1050
1051
1052
1053
1054
1055
1056

1057
1058
1059
1060

1061
1062
1063
1064
1065
1066
1067

1068
1069
1070
1071

1072
1073
1074
1075
1076

1077
1078
1079
1080
1081

1082
1083
1084
1085
1086


1087
1088
1089
1090
1091
1092
1093
1094
1095







-
+



-
+






-
+


+
-
+




-
+




-
+




-
-
+
+







}

- (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 ((OFStream <OFReadyForReadingObserving> *)_stream)
	return _socket.fileDescriptorForReading;
	    .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

@implementation OFHTTPClientSyncPerformer
- (instancetype)initWithClient: (OFHTTPClient *)client
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128



1129
1130
1131
1132
1133
1134















1135
1136
1137
1138
1139
1140
1141
1148
1149
1150
1151
1152
1153
1154



1155
1156
1157
1158
1159




1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181







-
-
-
+
+
+


-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+








	[_delegate     client: client
	    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:didCreateSocket:request:)])
		[_delegate   client: client
		    didCreateSocket: sock
			    request: request];
	    @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:didCreateTLSStream:request:)])
		[_delegate	client: client
		    didCreateTLSStream: TLSStream
			       request: request];
}

-     (void)client: (OFHTTPClient *)client
  wantsRequestBody: (OFStream *)body
	   request: (OFHTTPRequest *)request
{
	if ([_delegate respondsToSelector:
1242
1243
1244
1245
1246
1247
1248
1249
1250


1251
1252
1253
1254
1255
1256
1257
1258
1282
1283
1284
1285
1286
1287
1288


1289
1290
1291
1292
1293
1294
1295
1296
1297
1298







-
-
+
+








		 redirects: redirects] autorelease] start];

	objc_autoreleasePoolPop(pool);
}

- (void)close
{
	[_socket release];
	_socket = nil;
	[_stream release];
	_stream = nil;

	[_lastURL release];
	_lastURL = nil;

	[_lastResponse release];
	_lastResponse = nil;
}
@end

Modified src/OFHTTPServer.m from [fa53a5562c] to [9d3b80f535].

25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
25
26
27
28
29
30
31

32
33
34
35
36
37
38







-







#import "OFDate.h"
#import "OFDictionary.h"
#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"
#import "OFInvalidArgumentException.h"
#import "OFInvalidEncodingException.h"

Modified src/OFTCPSocket.m from [d37354390e] to [cfa63a9a83].

49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
49
50
51
52
53
54
55


56
57
58
59
60
61
62







-
-







#import "OFNotImplementedException.h"
#import "OFNotOpenException.h"
#import "OFSetOptionFailedException.h"

static const OFRunLoopMode connectRunLoopMode =
    @"OFTCPSocketConnectRunLoopMode";

Class OFTLSSocketClass = Nil;

static OFString *defaultSOCKS5Host = nil;
static uint16_t defaultSOCKS5Port = 1080;

@interface OFTCPSocket () <OFIPSocketAsyncConnecting>
@end

@interface OFTCPSocketConnectDelegate: OFObject <OFTCPSocketDelegate>
185
186
187
188
189
190
191
192

193
194
195
196
197
198
199
200
201


202
203
204
205
206
207
208
209
210
211
212
213
214
183
184
185
186
187
188
189

190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205


206
207
208
209
210
211
212







-
+









+
+




-
-







{
	void *pool = objc_autoreleasePoolPush();
	id <OFTCPSocketDelegate> 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
{
	[self asyncConnectToHost: host
			    port: port

Modified src/OFTCPSocketSOCKS5Connector.h from [cfa41043ad] to [a3decd0834].

18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
18
19
20
21
22
23
24

25
26
27
28
29
30
31







-







OF_ASSUME_NONNULL_BEGIN

@class OFString;

@interface OFTCPSocketSOCKS5Connector: OFObject <OFTCPSocketDelegate>
{
	OFTCPSocket *_socket;
	Class _socketClass;
	OFString *_host;
	uint16_t _port;
	id <OFTCPSocketDelegate> _Nullable _delegate;
#ifdef OF_HAVE_BLOCKS
	OFTCPSocketAsyncConnectBlock _Nullable _block;
#endif
	id _Nullable _exception;

Modified src/OFTCPSocketSOCKS5Connector.m from [a3d986247b] to [cef61681e6].

50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
50
51
52
53
54
55
56








57
58
59
60
61
62
63
64
65
66
67
68
69
70


71
72
73
74
75
76
77
78
79
80
81
82
83
84
85

86
87
88
89
90
91
92







-
-
-
-
-
-
-
-














-
-















-







		_host = [host copy];
		_port = port;
		_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;
	}

	return self;
}

- (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];
#endif
	[_exception release];
	[_request release];

	[super dealloc];
}

- (void)didConnect
{
	_socket.delegate = _delegate;
	object_setClass(_socket, _socketClass);

#ifdef OF_HAVE_BLOCKS
	if (_block != NULL)
		_block(_exception);
	else {
#endif
		if ([_delegate respondsToSelector:

Renamed and modified src/OFTLSSocket.h [5dd4f959ef] to src/OFTLSStream.h [c91283e053].

9
10
11
12
13
14
15
16


17
18
19




20
21

22
23

24
25













26
27
28
29

30
31

32
33

34
35
36
37



38
39
40
41



42
43

44
45
46


47


48
49

50
51
52






53

54
55
56
57
58
59

60
61
62

63
64
65
66
67


68
69
70


71
72
73
74
75



76
77


78
79

80

81
82
83

84
85


86
87


88
89
90
91




92
93

94
95
96

97




98
99


100
101
102

103


104
105

106
107
108
109
110
111
112





113
114

115
116
117
118
119
9
10
11
12
13
14
15

16
17
18
19
20
21
22
23
24
25

26
27

28
29

30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

46
47

48
49

50
51
52


53
54
55
56



57
58
59


60

61

62
63
64
65
66
67

68
69
70
71
72
73
74
75
76
77

78
79
80
81
82
83

84
85
86

87


88
89
90
91
92
93


94
95
96




97
98
99
100

101
102
103
104
105

106

107

108


109
110
111

112
113
114
115


116
117
118
119
120

121
122
123

124

125
126
127
128
129

130
131
132
133

134

135
136
137

138
139
140
141
142
143
144

145
146
147
148
149
150

151
152
153
154
155
156







-
+
+



+
+
+
+

-
+

-
+

-
+
+
+
+
+
+
+
+
+
+
+
+
+



-
+

-
+

-
+


-
-
+
+
+

-
-
-
+
+
+
-
-
+
-

-
+
+

+
+

-
+



+
+
+
+
+
+
-
+





-
+


-
+
-
-



+
+

-
-
+
+

-
-
-
-
+
+
+

-
+
+


+
-
+
-

-
+
-
-
+
+

-
+
+


-
-
+
+
+
+

-
+


-
+
-
+
+
+
+

-
+
+


-
+
-
+
+

-
+






-
+
+
+
+
+

-
+





 *
 * 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"
#import "OFStream.h"
#import "OFRunLoop.h"

OF_ASSUME_NONNULL_BEGIN

/** @file */

@class OFTLSStream;

/**
 * @protocol OFTLSSocketDelegate OFTLSSocket.h ObjFW/OFTLSSocket.h
 * @protocol OFTLSStreamDelegate OFTLSStream.h ObjFW/OFTLSStream.h
 *
 * A delegate for OFTLSSocket.
 * A delegate for OFTLSStream.
 */
@protocol OFTLSSocketDelegate <OFTCPSocketDelegate>
@protocol OFTLSStreamDelegate <OFStreamDelegate>
/**
 * @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 OFTLSSocket OFTLSSocket.h ObjFW/OFTLSSocket.h
 * @class OFTLSStream OFTLSStream.h ObjFW/OFTLSStream.h
 *
 * @brief A class that provides Transport Layer Security on top of a TCP socket.
 * @brief A class that provides Transport Layer Security on top of a stream.
 *
 * This class is a class cluster and returns a suitable OFTLSSocket subclass,
 * This class is a class cluster and returns a suitable OFTLSStream subclass,
 * if available.
 *
 * Subclasses need to override @ref accept, @ref lowlevelReadIntoBuffer:length:,
 * @ref lowlevelWriteBuffer:length: and @ref startTLSForHost:port:. The method
 * 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
 * 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
 * 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
 * the class is abstract), the private methods @ref TCPAccept,
 * @ref lowlevelTCPReadIntoBuffer:length: and
 * be used.
 * @ref lowlevelTCPWriteBuffer:length: are provided.
 */
@interface OFTLSSocket: OFTCPSocket
@interface OFTLSStream: OFStream <OFReadyForReadingObserving,
    OFReadyForWritingObserving>
{
	OFStream <OFReadyForReadingObserving, OFReadyForWritingObserving>
	    *_wrappedStream;
	bool _verifiesCertificates;
	OF_RESERVE_IVARS(OFTLSSocket, 4)
	OF_RESERVE_IVARS(OFTLSStream, 4)
}

/**
 * @brief The wrapped stream.
 */
@property (readonly, nonatomic) OFStream <OFReadyForReadingObserving,
    OFReadyForWritingObserving> *wrappedStream;

/**
 * @brief The delegate for asynchronous operations on the socket.
 * @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 <OFTLSSocketDelegate> delegate;
    id <OFTLSStreamDelegate> delegate;

/**
 * @brief Whether certificates are verified.
 * @brief Whether certificates are verified. Default is true.
 *
 * The default is enabled.
 */
@property (nonatomic) bool verifiesCertificates;

- (instancetype)init OF_UNAVAILABLE;

/**
 * @brief Initializes the TLS socket with the specified TCP socket as its
 *	  underlying socket.
 * @brief Creates a new TLS stream with the specified stream as its underlying
 *	  stream.
 *
 * 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
 * @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)initWithSocket: (OFTCPSocket *)socket;
+ (instancetype)streamWithStream: (OFStream <OFReadyForReadingObserving,
				       OFReadyForWritingObserving> *)stream;

/**
 * @brief Initializes the TLS stream with the specified stream as its
 * @brief Start TLS on the underlying socket with the assumption that it is
 *	  underlying stream.
 *	  connected to the specified host and port.
 *
 * @param host The host the socket is connected to, which is also used for
 * @param stream The stream to use as underlying stream. Must not be closed
 *	       verification
 * @param port The port the socket is connected to
 *		 before the TLS stream is closed.
 * @return An initialized TLS stream
 */
- (void)startTLSForHost: (OFString *)host port: (uint16_t)port;
- (instancetype)initWithStream: (OFStream <OFReadyForReadingObserving,
				     OFReadyForWritingObserving> *)stream;

/**
 * @brief This method should never be called directly. Only subclasses of
 *	  @ref OFTLSSocket are allowed to call it.
 * @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
 */
- (instancetype)TCPAccept;
- (void)asyncPerformClientHandshakeWithHost: (OFString *)host;

/**
 * @brief This method should never be called directly. Only subclasses of
 * @brief Asynchronously performs the TLS client handshake for the specified
 *	  @ref OFTLSSocket are allowed to call it.
 *	  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
 */
- (size_t)lowlevelTCPReadIntoBuffer: (void *)buffer length: (size_t)length;
- (void)asyncPerformClientHandshakeWithHost: (OFString *)host
				runLoopMode: (OFRunLoopMode)runLoopMode;

/**
 * @brief This method should never be called directly. Only subclasses of
 * @brief Performs the TLS client handshake for the specified host.
 *	  @ref OFTLSSocket are allowed to call it.
 *
 * @param host The host to perform the handshake with
 */
- (size_t)lowlevelTCPWriteBuffer: (const void *)buffer length: (size_t)length;
- (void)performClientHandshakeWithHost: (OFString *)host;
@end

#ifdef __cplusplus
extern "C" {
#endif
/**
 * @brief The concrete subclass of OFTLSSocket that should be used.
 * @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 _Nullable OFTLSSocketImplementation;
extern Class OFTLSStreamImplementation;
#ifdef __cplusplus
}
#endif

OF_ASSUME_NONNULL_END

Renamed and modified src/OFTLSSocket.m [dab6e3fd2b] to src/OFTLSStream.m [53f8754aca].

11
12
13
14
15
16
17
18
19


20
21
22
23
24
25
26



27
28

29

30
31
32
33
34
35
36
37















38
39





40
41
42
43
44
45
46




















47











48
49
50
51

52
53
54
55

56
57
58
59
60
61
62
63
64
65
66
67
68
69
70

71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205





206


207
208

209
210




211


212


213





214
215





216


217





218
219







220


221
222
223







224
225
11
12
13
14
15
16
17


18
19

20


21
22

23
24
25
26

27
28
29








30
31
32
33
34
35
36
37
38
39
40
41
42
43
44


45
46
47
48
49
50
51





52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86

87




88

89
90
91
92
93
94
95
96
97
98




99

100
101
102
103
























































































































104
105
106
107
108
109
110
111
112
113
114
115
116
117
118

119
120
121

122
123
124
125
126
127
128

129
130
131
132
133

134
135
136
137
138
139
140
141
142
143
144
145

146
147
148
149
150
151
152
153


154
155
156
157
158
159
160
161
162
163



164
165
166
167
168
169
170
171
172







-
-
+
+
-

-
-


-
+
+
+

-
+

+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
+
+


-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+

+
+
+
+
+
+
+
+
+
+
+



-
+
-
-
-
-
+
-










-
-
-
-
+
-




-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-










+
+
+
+
+
-
+
+

-
+


+
+
+
+
-
+
+

+
+
-
+
+
+
+
+


+
+
+
+
+
-
+
+

+
+
+
+
+
-
-
+
+
+
+
+
+
+

+
+
-
-
-
+
+
+
+
+
+
+


 * 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 "OFTLSStream.h"
#import "OFDate.h"
#import "OFSocket+Private.h"

#import "OFInitializationFailedException.h"
#import "OFInvalidArgumentException.h"
#import "OFNotImplementedException.h"

Class OFTLSSocketImplementation = Nil;
Class OFTLSStreamImplementation = Nil;
static const OFRunLoopMode handshakeRunLoopMode =
    @"OFTLSStreamHandshakeRunLoopMode";

@interface OFTLSSocketAsyncConnector: OFObject <OFTLSSocketDelegate>
@interface OFTLSStreamHandshakeDelegate: OFObject <OFTLSStreamDelegate>
{
@public
	OFTLSSocket *_socket;
	OFString *_host;
	uint16_t _port;
	id <OFTLSSocketDelegate> _delegate;
}

- (instancetype)initWithSocket: (OFTLSSocket *)sock
			  host: (OFString *)host
	bool _done;
	id _exception;
}
@end

@implementation OFTLSStreamHandshakeDelegate
- (void)dealloc
{
	[_exception release];

	[super dealloc];
}

-		       (void)stream: (OFTLSStream *)stream
  didPerformClientHandshakeWithHost: (OFString *)host
			  port: (uint16_t)port
		      delegate: (id <OFTLSSocketDelegate>)delegate;
			  exception: (id)exception
{
	_done = true;
	_exception = [exception retain];
}
@end

@implementation OFTLSSocketAsyncConnector
- (instancetype)initWithSocket: (OFTLSSocket *)sock
			  host: (OFString *)host
			  port: (uint16_t)port
		      delegate: (id <OFTLSSocketDelegate>)delegate
@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 <OFReadyForReadingObserving,
				       OFReadyForWritingObserving> *)stream
{
	return [[[self alloc] initWithStream: stream] autorelease];
}

- (instancetype)init
{
	OF_INVALID_INIT_METHOD
}

- (instancetype)initWithStream: (OFStream <OFReadyForReadingObserving,
				     OFReadyForWritingObserving> *)stream
{
	self = [super init];

	@try {
		_socket = [sock retain];
		_wrappedStream = [stream retain];
		_host = [host copy];
		_port = port;
		_delegate = [delegate retain];

		_verifiesCertificates = true;
		_socket.delegate = self;
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	if (_socket.delegate == self)
		_socket.delegate = _delegate;

	[_socket release];
	[_wrappedStream 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
}

- (bool)hasDataInReadBuffer
{
	return (super.hasDataInReadBuffer ||
	    _wrappedStream.hasDataInReadBuffer);
}
- (instancetype)TCPAccept

- (bool)lowlevelIsAtEndOfStream
{
	return [super accept];
	return _wrappedStream.atEndOfStream;
}

- (int)fileDescriptorForReading
{
	return _wrappedStream.fileDescriptorForReading;
}
- (size_t)lowlevelTCPReadIntoBuffer: (void *)buffer length: (size_t)length

- (int)fileDescriptorForWriting
{
	return _wrappedStream.fileDescriptorForWriting;
}
	return [super lowlevelReadIntoBuffer: buffer length: length];

- (void)asyncPerformClientHandshakeWithHost: (OFString *)host
{
	[self asyncPerformClientHandshakeWithHost: host
				      runLoopMode: OFDefaultRunLoopMode];
}

- (void)asyncPerformClientHandshakeWithHost: (OFString *)host
				runLoopMode: (OFRunLoopMode)runLoopMode
{
	OF_UNRECOGNIZED_SELECTOR
}
- (size_t)lowlevelTCPWriteBuffer: (const void *)buffer length: (size_t)length

- (void)performClientHandshakeWithHost: (OFString *)host
{
	void *pool = objc_autoreleasePoolPush();
	id <OFTLSStreamDelegate> delegate = _delegate;
	OFTLSStreamHandshakeDelegate *handshakeDelegate =
	    [[[OFTLSStreamHandshakeDelegate alloc] init] autorelease];
	OFRunLoop *runLoop = [OFRunLoop currentRunLoop];
	return [super lowlevelWriteBuffer: buffer length: length];
}

	_delegate = handshakeDelegate;
	[self asyncPerformClientHandshakeWithHost: host
				      runLoopMode: handshakeRunLoopMode];

	while (!handshakeDelegate->_done)
		[runLoop runMode: handshakeRunLoopMode beforeDate: nil];

	/* Cleanup */
	[runLoop runMode: handshakeRunLoopMode beforeDate: [OFDate date]];
- (bool)lowlevelTCPIsAtEndOfStream
{
	return [super lowlevelIsAtEndOfStream];

	_delegate = delegate;

	if (handshakeDelegate->_exception != nil)
		@throw handshakeDelegate->_exception;

	objc_autoreleasePoolPop(pool);
}
@end

Modified src/ObjFW.h from [a6b2ac582f] to [b540b51725].

72
73
74
75
76
77
78
79

80
81
82
83
84
85
86
72
73
74
75
76
77
78

79
80
81
82
83
84
85
86







-
+







#endif
#ifdef OF_HAVE_SOCKETS
# 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"
# ifdef OF_HAVE_IPX
#  import "OFIPXSocket.h"

Modified utils/ofhttp/OFHTTP.m from [2fe08b11b5] to [6bac196b77].

29
30
31
32
33
34
35
36

37
38
39
40
41
42
43
29
30
31
32
33
34
35

36
37
38
39
40
41
42
43







-
+







#ifdef OF_HAVE_PLUGINS
# import "OFPlugin.h"
#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"
#import "OFInvalidFormatException.h"
#import "OFInvalidServerReplyException.h"
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
274
275
276
277
278
279
280











281
282
283
284
285
286
287







-
-
-
-
-
-
-
-
-
-
-








	[fileName retain];
	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 {
		_method = OFHTTPRequestMethodGet;

532
533
534
535
536
537
538
539

540
541
542
543
544
545
546
521
522
523
524
525
526
527

528
529
530
531
532
533
534
535







-
+







		[sandbox unveilPath: outputPath
			permissions: (_continue ? @"rwc" : @"wc")];
	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

	_outputPath = [outputPath copy];
579
580
581
582
583
584
585
586
587
588



589
590
591

592
593
594
595
596
597
598
568
569
570
571
572
573
574



575
576
577
578


579
580
581
582
583
584
585
586







-
-
-
+
+
+

-
-
+







		_HTTPClient.allowsInsecureRedirects = true;

	_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
{
	/* TODO: Do asynchronously and print status */
840
841
842
843
844
845
846
847
848
849
850
851






852
853
854
855
856
857
858
828
829
830
831
832
833
834





835
836
837
838
839
840
841
842
843
844
845
846
847







-
-
-
-
-
+
+
+
+
+
+







			    @"prog", [OFApplication programName],
			    @"url", request.URL.string)];
		} 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",
			    @"Read or write failed");

Modified utils/ofhttp/lang/de.json from [ad71562e8d] to [027af9bd4c].

48
49
50
51
52
53
54
55
56
57
58
59







60
61
62
63
64
65
66
48
49
50
51
52
53
54





55
56
57
58
59
60
61
62
63
64
65
66
67
68







-
-
-
-
-
+
+
+
+
+
+
+







        "%[prog]: Fehler beim Download von <%[url]>!\n",
        "  Verbindung fehlgeschlagen: %[exception]"
    ],
    "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": [
        "%[prog]: Fehler beim Download von <%[url]>!\n",
        "  %[error]: %[exception]"