ObjFW  Check-in [aac504a7bc]

Overview
Comment:OFHTTPClient: Support for sending chunked body
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: aac504a7bcea53ce5c069997aaa534131853b244c821b9e6c22a08fe7f218082
User & Date: js on 2020-03-25 21:26:09
Other Links: manifest | tags
Context
2020-03-28
14:35
OFHTTPServer: Support for chunked request bodies check-in: 8faade8d19 user: js tags: trunk
2020-03-25
21:26
OFHTTPClient: Support for sending chunked body check-in: aac504a7bc user: js tags: trunk
2020-03-23
01:03
of_dns_record_type_parse(): Allow ALL check-in: 103f04241c user: js tags: trunk
Changes

Modified src/OFHTTPClient.m from [36ad6829ee] to [6a00e9f50f].

72
73
74
75
76
77
78

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







+







- (void)closeAndReconnect;
@end

@interface OFHTTPClientRequestBodyStream: OFStream <OFReadyForWritingObserving>
{
	OFHTTPClientRequestHandler *_handler;
	OFTCPSocket *_socket;
	bool _chunked;
	uintmax_t _toWrite;
	bool _atEndOfStream;
}

- (instancetype)initWithHandler: (OFHTTPClientRequestHandler *)handler
			 socket: (OFTCPSocket *)sock;
@end
546
547
548
549
550
551
552



553
554
555
556
557
558
559
560
561
562
563
564
565
566
567




568

569
570
571
572
573
574
575
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575

576
577
578
579
580
581
582
583







+
+
+















+
+
+
+
-
+








- (OFString *)stream: (OFStream *)stream
      didWriteString: (OFString *)string
	    encoding: (of_string_encoding_t)encoding
	bytesWritten: (size_t)bytesWritten
	   exception: (id)exception
{
	OFDictionary OF_GENERIC(OFString *, OFString *) *headers;
	OFString *transferEncoding;

	if (exception != nil) {
		if ([exception isKindOfClass: [OFWriteFailedException class]] &&
		    ([exception errNo] == ECONNRESET ||
		    [exception errNo] == EPIPE)) {
			/* In case a keep-alive connection timed out */
			[self closeAndReconnect];
			return nil;
		}

		[self raiseException: exception];
		return nil;
	}

	_firstLine = true;

	headers = _request.headers;
	transferEncoding = [headers objectForKey: @"Transfer-Encoding"];

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

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

		if ([_client->_delegate respondsToSelector:
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
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







-
+






+
+
+

-
-
+
+
+
+

-
-
-
+
+
+

-
-
+
+
-







			 socket: (OFTCPSocket *)sock
{
	self = [super init];

	@try {
		OFDictionary OF_GENERIC(OFString *, OFString *) *headers;
		intmax_t contentLength;
		OFString *contentLengthString;
		OFString *transferEncoding, *contentLengthString;

		_handler = [handler retain];
		_socket = [sock retain];

		headers = _handler->_request.headers;

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

		contentLengthString = [headers objectForKey: @"Content-Length"];
		if (contentLengthString == nil)
			@throw [OFInvalidArgumentException exception];
		if (contentLengthString != nil) {
			if (_chunked)
				@throw [OFInvalidArgumentException
				    exception];

		contentLength = contentLengthString.decimalValue;
		if (contentLength < 0)
			@throw [OFOutOfRangeException exception];
			contentLength = contentLengthString.decimalValue;
			if (contentLength < 0)
				@throw [OFOutOfRangeException exception];

		_toWrite = contentLength;

			_toWrite = contentLength;
		} else if (!_chunked)
		if ([headers objectForKey: @"Transfer-Encoding"] != nil)
			@throw [OFInvalidArgumentException exception];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
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



773
774
775
776
777
778
779
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







+
+
+
+
+
+
+
+







+
+
-
+








+
-
+

-
-
+
+
+







{
	size_t requestedLength = length;
	size_t ret;

	if (_socket == 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: requestedLength
			   bytesWritten: 0
				  errNo: 0];

	if (_chunked)
		[_socket writeFormat: @"%zX\r\n", length];
	if (length > _toWrite)
	else if (length > _toWrite)
		length = (size_t)_toWrite;

	ret = [_socket writeBuffer: buffer
			    length: length];

	if (ret > length)
		@throw [OFOutOfRangeException exception];

	if (!_chunked) {
	_toWrite -= ret;
		_toWrite -= ret;

	if (_toWrite == 0)
		_atEndOfStream = true;
		if (_toWrite == 0)
			_atEndOfStream = true;
	}

	if (requestedLength > length)
		@throw [OFWriteFailedException
		    exceptionWithObject: self
			requestedLength: requestedLength
			   bytesWritten: ret
				  errNo: 0];
787
788
789
790
791
792
793


794

795
796
797
798
799
800
801
811
812
813
814
815
816
817
818
819

820
821
822
823
824
825
826
827







+
+
-
+







}

- (void)close
{
	if (_socket == nil)
		@throw [OFNotOpenException exceptionWithObject: self];

	if (_chunked)
		[_socket writeString: @"0\r\n"];
	if (_toWrite > 0)
	else if (_toWrite > 0)
		@throw [OFTruncatedDataException exception];

	_socket.delegate = _handler;
	[_socket asyncReadLine];

	[_socket release];
	_socket = nil;
836
837
838
839
840
841
842



843
844
845
846
847
848
849
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878







+
+
+







	super.headers = headers;

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

	contentLength = [headers objectForKey: @"Content-Length"];
	if (contentLength != nil) {
		if (_chunked)
			@throw [OFInvalidServerReplyException exception];

		_hasContentLength = true;

		@try {
			intmax_t toRead = contentLength.decimalValue;

			if (toRead < 0)
				@throw [OFInvalidServerReplyException

Modified utils/ofhttp/OFHTTP.m from [7982cd7599] to [cb6f1d9fe7].

88
89
90
91
92
93
94


95
96
97
98
99
100
101
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103







+
+








	if (full) {
		[stream writeString: @"\n"];
		[stream writeLine: OF_LOCALIZED(@"full_usage",
		    @"Options:\n    "
		    @"-b  --body           "
		    @"  Specify the file to send as body\n    "
		    @"                     "
		    @"  (- for standard input)\n    "
		    @"-c  --continue       "
		    @"  Continue download of existing file\n    "
		    @"-f  --force          "
		    @"  Force / overwrite existing file\n    "
		    @"-h  --help           "
		    @"  Show this help\n    "
		    @"-H  --header         "
316
317
318
319
320
321
322
323

324
325





326
327


328

329
330
331
332














333
334
335
336
337
338
339
318
319
320
321
322
323
324

325
326
327
328
329
330
331
332


333
334
335
336




337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357







-
+


+
+
+
+
+
-
-
+
+

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








	[_clientHeaders setObject: value
			   forKey: name];
}

- (void)setBody: (OFString *)path
{
	uintmax_t bodySize;
	OFString *contentLength = nil;

	[_body release];
	_body = nil;

	if ([path isEqual: @"-"])
		_body = [of_stdin copy];
	else {
	_body = [[OFFile alloc] initWithPath: path
					mode: @"r"];
		_body = [[OFFile alloc] initWithPath: path
						mode: @"r"];

		@try {
	bodySize = [[OFFileManager defaultManager] attributesOfItemAtPath: path]
	    .fileSize;
	[_clientHeaders setObject: [OFString stringWithFormat: @"%ju", bodySize]
			   forKey: @"Content-Length"];
			uintmax_t fileSize = [[OFFileManager defaultManager]
			    attributesOfItemAtPath: path].fileSize;

			contentLength =
			    [OFString stringWithFormat: @"%ju", fileSize];
			[_clientHeaders setObject: contentLength
					   forKey: @"Content-Length"];
		} @catch (OFRetrieveItemAttributesFailedException *e) {
		}
	}

	if (contentLength == nil)
		[_clientHeaders setObject: @"chunked"
				   forKey: @"Transfer-Encoding"];
}

- (void)setMethod: (OFString *)method
{
	void *pool = objc_autoreleasePoolPush();

	method = method.uppercaseString;

Modified utils/ofhttp/lang/de.json from [ed88980c09] to [9f2302952b].

1
2
3
4
5

6
7
8
9
10
11
12
1
2
3
4
5
6
7
8
9
10
11
12
13





+







{
    "usage": "Benutzung: %[prog] -[cehHmoOPqv] url1 [url2 ...]",
    "full_usage": [
        "Optionen:\n",
        "    -b  --body             Angegebene Datei als Body übergeben\n",
        "                           (- für Standard-Eingabe)\n",
        "    -c  --continue         Download von existierender Datei ",
        "fortsetzen\n",
        "    -f  --force            Existierende Datei überschreiben\n",
        "    -h  --help             Diese Hilfe anzeigen\n",
        "    -H  --header           Einen Header (z.B. X-Foo:Bar) hinzufügen\n",
        "    -m  --method           HTTP Request-Methode setzen\n",
        "    -o  --output           Ausgabe-Dateiname angeben\n",