ObjFW  Check-in [c195c1f098]

Overview
Comment:OFHTTPClient: Implement "Connection: keep-alive".
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: c195c1f098b9a941c2ed97e3d41c541d01bfefeb6e023b4ce0ee9ae2bec485ee
User & Date: js on 2013-03-15 17:04:17
Other Links: manifest | tags
Context
2013-03-16
10:13
Add forwardingTargetForSelector: for AMD64/ELF. check-in: 5a6cc94418 user: js tags: trunk
2013-03-15
17:04
OFHTTPClient: Implement "Connection: keep-alive". check-in: c195c1f098 user: js tags: trunk
2013-03-14
20:20
Tag class pointers for @compatibility_aliases. check-in: 69d9f76520 user: js tags: trunk
Changes

Modified src/OFHTTPClient.h from [157d08fbae] to [838f77c97e].

87
88
89
90
91
92
93



94
95
96
97
98
99
100
/*!
 * @brief A class for performing HTTP requests.
 */
@interface OFHTTPClient: OFObject
{
	id <OFHTTPClientDelegate> _delegate;
	bool _insecureRedirectsAllowed;



}

#ifdef OF_HAVE_PROPERTIES
@property (assign) id <OFHTTPClientDelegate> delegate;
@property bool insecureRedirectsAllowed;
#endif








>
>
>







87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/*!
 * @brief A class for performing HTTP requests.
 */
@interface OFHTTPClient: OFObject
{
	id <OFHTTPClientDelegate> _delegate;
	bool _insecureRedirectsAllowed;
	OFTCPSocket *_socket;
	OFURL *_lastURL;
	OFHTTPRequestReply *_lastReply;
}

#ifdef OF_HAVE_PROPERTIES
@property (assign) id <OFHTTPClientDelegate> delegate;
@property bool insecureRedirectsAllowed;
#endif

145
146
147
148
149
150
151





152
153
154
155
 * @param redirects The maximum number of redirects after which no further
 *		    attempt is done to follow the redirect, but instead the
 *		    redirect is returned as an OFHTTPRequestReply
 * @return An OFHTTPRequestReply with the reply of the HTTP request
 */
- (OFHTTPRequestReply*)performRequest: (OFHTTPRequest*)request
			    redirects: (size_t)redirects;





@end

@interface OFObject (OFHTTPClientDelegate) <OFHTTPClientDelegate>
@end







>
>
>
>
>




148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
 * @param redirects The maximum number of redirects after which no further
 *		    attempt is done to follow the redirect, but instead the
 *		    redirect is returned as an OFHTTPRequestReply
 * @return An OFHTTPRequestReply with the reply of the HTTP request
 */
- (OFHTTPRequestReply*)performRequest: (OFHTTPRequest*)request
			    redirects: (size_t)redirects;

/*!
 * @brief Closes connections that are still open due to keep-alive.
 */
- (void)close;
@end

@interface OFObject (OFHTTPClientDelegate) <OFHTTPClientDelegate>
@end

Modified src/OFHTTPClient.m from [4a71698195] to [af7cb290e3].

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
 * file.
 */

#include "config.h"

#include <string.h>
#include <ctype.h>



#import "OFHTTPClient.h"
#import "OFHTTPRequest.h"
#import "OFHTTPRequestReply.h"
#import "OFString.h"
#import "OFURL.h"
#import "OFTCPSocket.h"
#import "OFDictionary.h"
#import "OFDataArray.h"

#import "OFHTTPRequestFailedException.h"
#import "OFInvalidEncodingException.h"
#import "OFInvalidFormatException.h"
#import "OFInvalidServerReplyException.h"

#import "OFOutOfMemoryException.h"
#import "OFOutOfRangeException.h"

#import "OFTruncatedDataException.h"
#import "OFUnsupportedProtocolException.h"
#import "OFUnsupportedVersionException.h"


#import "autorelease.h"
#import "macros.h"

static OF_INLINE void
normalize_key(char *str_)
{







>
>














>


>



>







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
 * file.
 */

#include "config.h"

#include <string.h>
#include <ctype.h>

#include <errno.h>

#import "OFHTTPClient.h"
#import "OFHTTPRequest.h"
#import "OFHTTPRequestReply.h"
#import "OFString.h"
#import "OFURL.h"
#import "OFTCPSocket.h"
#import "OFDictionary.h"
#import "OFDataArray.h"

#import "OFHTTPRequestFailedException.h"
#import "OFInvalidEncodingException.h"
#import "OFInvalidFormatException.h"
#import "OFInvalidServerReplyException.h"
#import "OFNotConnectedException.h"
#import "OFOutOfMemoryException.h"
#import "OFOutOfRangeException.h"
#import "OFReadFailedException.h"
#import "OFTruncatedDataException.h"
#import "OFUnsupportedProtocolException.h"
#import "OFUnsupportedVersionException.h"
#import "OFWriteFailedException.h"

#import "autorelease.h"
#import "macros.h"

static OF_INLINE void
normalize_key(char *str_)
{
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
		str++;
	}
}

@interface OFHTTPClientReply: OFHTTPRequestReply
{
	OFTCPSocket *_socket;
	bool _chunked, _atEndOfStream;
	size_t _toRead;
}

- initWithSocket: (OFTCPSocket*)socket;

@end

@implementation OFHTTPClientReply
- initWithSocket: (OFTCPSocket*)socket
{
	self = [super init];

	_socket = [socket retain];

	return self;
}






- (void)dealloc
{
	[_socket release];

	[super dealloc];
}

- (void)setHeaders: (OFDictionary*)headers
{


	[super setHeaders: headers];

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












}

- (size_t)lowlevelReadIntoBuffer: (void*)buffer
			  length: (size_t)length
{





















	if (!_chunked)












		return [_socket readIntoBuffer: buffer



					length: length];







	if (_toRead > 0) {
		if (length > _toRead)
			length = _toRead;

		length = [_socket readIntoBuffer: buffer
					  length: length];








|




>











>
>
>
>
>










>
>




>
>
>
>
>
>
>
>
>
>
>
>





>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
|
>
>
>
|

>
>
>
>
>
>







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
		str++;
	}
}

@interface OFHTTPClientReply: OFHTTPRequestReply
{
	OFTCPSocket *_socket;
	bool _hasContentLength, _chunked, _keepAlive, _atEndOfStream;
	size_t _toRead;
}

- initWithSocket: (OFTCPSocket*)socket;
- (void)setKeepAlive: (bool)keepAlive;
@end

@implementation OFHTTPClientReply
- initWithSocket: (OFTCPSocket*)socket
{
	self = [super init];

	_socket = [socket retain];

	return self;
}

- (void)setKeepAlive: (bool)keepAlive
{
	_keepAlive = keepAlive;
}

- (void)dealloc
{
	[_socket release];

	[super dealloc];
}

- (void)setHeaders: (OFDictionary*)headers
{
	OFString *contentLength;

	[super setHeaders: headers];

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

	contentLength = [headers objectForKey: @"Content-Length"];
	if (contentLength != nil) {
		_hasContentLength = true;

		@try {
			_toRead = [contentLength decimalValue];
		} @catch (OFInvalidFormatException *e) {
			@throw [OFInvalidServerReplyException
			    exceptionWithClass: [self class]];
		}
	}
}

- (size_t)lowlevelReadIntoBuffer: (void*)buffer
			  length: (size_t)length
{
	if (_atEndOfStream) {
		OFReadFailedException *e;

		e = [OFReadFailedException exceptionWithClass: [self class]
						       stream: self
					      requestedLength: length];

#ifndef _WIN32
		e->_errNo = ENOTCONN;
#else
		e->_errNo = WSAENOTCONN;
#endif

		@throw e;
	}

	if (!_hasContentLength && !_chunked)
		return [_socket readIntoBuffer: buffer
					length: length];

	/* Content-Length */
	if (!_chunked) {
		size_t ret;

		if (_toRead == 0) {
			_atEndOfStream = true;

			if (!_keepAlive)
				[_socket close];

			return 0;
		}

		if (_toRead < length)
			ret = [_socket readIntoBuffer: buffer
					       length: _toRead];
		else
			ret = [_socket readIntoBuffer: buffer
					       length: length];

		_toRead -= ret;

		return ret;
	}

	/* Chunked */
	if (_toRead > 0) {
		if (length > _toRead)
			length = _toRead;

		length = [_socket readIntoBuffer: buffer
					  length: length];

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
			    (size_t)[line hexadecimalValue];
		} @catch (OFInvalidFormatException *e) {
			@throw [OFInvalidServerReplyException
			    exceptionWithClass: [self class]];
		}

		if (_toRead == 0) {
			[_socket close];
			_atEndOfStream = true;














		}

		objc_autoreleasePoolPop(pool);

		return 0;
	}
}

- (bool)lowlevelIsAtEndOfStream
{
	if (!_chunked)
		return [_socket isAtEndOfStream];

	return _atEndOfStream;
}

- (int)fileDescriptorForReading
{







<

>
>
>
>
>
>
>
>
>
>
>
>
>
>










|







207
208
209
210
211
212
213

214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
			    (size_t)[line hexadecimalValue];
		} @catch (OFInvalidFormatException *e) {
			@throw [OFInvalidServerReplyException
			    exceptionWithClass: [self class]];
		}

		if (_toRead == 0) {

			_atEndOfStream = true;

			if (_keepAlive) {
				@try {
					line = [_socket readLine];
				} @catch (OFInvalidEncodingException *e) {
					@throw [OFInvalidServerReplyException
					    exceptionWithClass: [self class]];
				}

				if ([line length] > 0)
					@throw [OFInvalidServerReplyException
					    exceptionWithClass: [self class]];
			} else
				[_socket close];
		}

		objc_autoreleasePoolPop(pool);

		return 0;
	}
}

- (bool)lowlevelIsAtEndOfStream
{
	if (!_hasContentLength && !_chunked)
		return [_socket isAtEndOfStream];

	return _atEndOfStream;
}

- (int)fileDescriptorForReading
{
180
181
182
183
184
185
186







187
188
189
190
191
192
193
@end

@implementation OFHTTPClient
+ (instancetype)client
{
	return [[[self alloc] init] autorelease];
}








- (void)setDelegate: (id <OFHTTPClientDelegate>)delegate
{
	_delegate = delegate;
}

- (id <OFHTTPClientDelegate>)delegate







>
>
>
>
>
>
>







260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
@end

@implementation OFHTTPClient
+ (instancetype)client
{
	return [[[self alloc] init] autorelease];
}

- (void)dealloc
{
	[self close];

	[super dealloc];
}

- (void)setDelegate: (id <OFHTTPClientDelegate>)delegate
{
	_delegate = delegate;
}

- (id <OFHTTPClientDelegate>)delegate
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245

246
247
248
249
250
251
252
253
254
255
256































257
258


259
260


261















262
263
264
265
266
267
268
269
270
271
272





273

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
299
300
301
302
303
304
305

306
307
308
309
310


311
312

313

314



315





316
317
318
319
320
321
322
323
324
325
326






















327
328
329
330
331
332
333

- (OFHTTPRequestReply*)performRequest: (OFHTTPRequest*)request
{
	return [self performRequest: request
			  redirects: 10];
}

- (OFHTTPRequestReply*)performRequest: (OFHTTPRequest*)request
			    redirects: (size_t)redirects
{
	void *pool = objc_autoreleasePoolPush();
	OFURL *URL = [request URL];
	OFString *scheme = [URL scheme];
	of_http_request_type_t requestType = [request requestType];
	OFDictionary *headers = [request headers];
	OFDataArray *POSTData = [request POSTData];
	OFTCPSocket *socket;
	OFHTTPClientReply *reply;
	OFString *line, *path, *version;
	OFMutableDictionary *serverHeaders;
	OFEnumerator *keyEnumerator, *objectEnumerator;
	OFString *key, *object;
	int status;
	const char *type = NULL;

	if (![scheme isEqual: @"http"] && ![scheme isEqual: @"https"])
		@throw [OFUnsupportedProtocolException
		    exceptionWithClass: [self class]
				   URL: URL];

	if ([scheme isEqual: @"http"])
		socket = [OFTCPSocket socket];
	else {
		if (of_tls_socket_class == Nil)
			@throw [OFUnsupportedProtocolException
			    exceptionWithClass: [self class]
					   URL: URL];

		socket = [[[of_tls_socket_class alloc] init] autorelease];

	}

	if ([_delegate respondsToSelector:
	    @selector(client:didCreateSocket:request:)])
		[_delegate client: self
		  didCreateSocket: socket
			  request: request];

	[socket connectToHost: [URL host]
			 port: [URL port]];
































	/*
	 * Work around a bug with packet splitting in lighttpd when using


	 * HTTPS.
	 */


	[socket setWriteBufferEnabled: true];
















	if (requestType == OF_HTTP_REQUEST_TYPE_GET)
		type = "GET";
	if (requestType == OF_HTTP_REQUEST_TYPE_HEAD)
		type = "HEAD";
	if (requestType == OF_HTTP_REQUEST_TYPE_POST)
		type = "POST";

	if ([(path = [URL path]) length] == 0)
		path = @"/";






	if ([URL query] != nil)

		[socket writeFormat: @"%s %@?%@ HTTP/%@\r\n",
		    type, path, [URL query], [request protocolVersionString]];
	else

		[socket writeFormat: @"%s %@ HTTP/%@\r\n",
		    type, path, [request protocolVersionString]];

	if ([URL port] == 80)
		[socket writeFormat: @"Host: %@\r\n", [URL host]];
	else
		[socket writeFormat: @"Host: %@:%d\r\n", [URL host],
		    [URL port]];

	[socket writeString: @"Connection: close\r\n"];

	if ([headers objectForKey: @"User-Agent"] == nil)

		[socket writeString: @"User-Agent: Something using ObjFW "
				     @"<https://webkeks.org/objfw>\r\n"];

	keyEnumerator = [headers keyEnumerator];
	objectEnumerator = [headers objectEnumerator];

	while ((key = [keyEnumerator nextObject]) != nil &&
	    (object = [objectEnumerator nextObject]) != nil)
		[socket writeFormat: @"%@: %@\r\n", key, object];

	if (requestType == OF_HTTP_REQUEST_TYPE_POST) {
		OFString *contentType = [request MIMEType];

		if (contentType == nil)
			contentType = @"application/x-www-form-urlencoded; "
			    @"charset=UTF-8\r\n";


		[socket writeFormat: @"Content-Type: %@\r\n", contentType];
		[socket writeFormat: @"Content-Length: %d\r\n",
		    [POSTData count] * [POSTData itemSize]];
	}



	[socket writeString: @"\r\n"];


	/* Work around a bug in lighttpd, see above */

	[socket flushWriteBuffer];



	[socket setWriteBufferEnabled: false];






	if (requestType == OF_HTTP_REQUEST_TYPE_POST)
		[socket writeBuffer: [POSTData items]
			     length: [POSTData count] * [POSTData itemSize]];

	@try {
		line = [socket readLine];
	} @catch (OFInvalidEncodingException *e) {
		@throw [OFInvalidServerReplyException
		    exceptionWithClass: [self class]];
	}























	if (![line hasPrefix: @"HTTP/"] || [line characterAtIndex: 8] != ' ')
		@throw [OFInvalidServerReplyException
		    exceptionWithClass: [self class]];

	version = [line substringWithRange: of_range(5, 3)];
	if (![version isEqual: @"1.0"] && ![version isEqual: @"1.1"])







|
<

<

<
<
<
<

<
<
<
<
<
<
<

<
<
|
<

|







|
>











>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
<
>
>
|
|
>
>
|
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>











>
>
>
>
>

>
|


>
|



|

|


<
<

>
|
|






|








>
|
|
|


>
>
|

>
|
>
|
>
>
>
|
>
>
>
>
>











>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>







294
295
296
297
298
299
300
301

302

303




304







305


306

307
308
309
310
311
312
313
314
315
316
317
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
358
359
360

361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413


414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
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

- (OFHTTPRequestReply*)performRequest: (OFHTTPRequest*)request
{
	return [self performRequest: request
			  redirects: 10];
}

- (OFTCPSocket*)OF_createSocketForRequest: (OFHTTPRequest*)request

{

	OFURL *URL = [request URL];




	OFTCPSocket *socket;










	[self close];


	if ([[URL scheme] isEqual: @"http"])
		socket = [OFTCPSocket socket];
	else {
		if (of_tls_socket_class == Nil)
			@throw [OFUnsupportedProtocolException
			    exceptionWithClass: [self class]
					   URL: URL];

		socket = [[[of_tls_socket_class alloc] init]
		    autorelease];
	}

	if ([_delegate respondsToSelector:
	    @selector(client:didCreateSocket:request:)])
		[_delegate client: self
		  didCreateSocket: socket
			  request: request];

	[socket connectToHost: [URL host]
			 port: [URL port]];

	return socket;
}

- (OFHTTPRequestReply*)performRequest: (OFHTTPRequest*)request
			    redirects: (size_t)redirects
{
	void *pool = objc_autoreleasePoolPush();
	OFURL *URL = [request URL];
	OFString *scheme = [URL scheme];
	of_http_request_type_t requestType = [request requestType];
	OFMutableString *requestString;
	OFDictionary *headers = [request headers];
	OFDataArray *POSTData = [request POSTData];
	OFTCPSocket *socket;
	OFHTTPClientReply *reply;
	OFString *line, *path, *version, *redirect, *keepAlive;
	OFMutableDictionary *serverHeaders;
	OFEnumerator *keyEnumerator, *objectEnumerator;
	OFString *key, *object;
	int status;
	const char *type = NULL;

	if (![scheme isEqual: @"http"] && ![scheme isEqual: @"https"])
		@throw [OFUnsupportedProtocolException
		    exceptionWithClass: [self class]
				   URL: URL];

	/* Can we reuse the socket? */
	if (_socket != nil && [[_lastURL scheme] isEqual: [URL scheme]] &&
	    [[_lastURL host] isEqual: [URL host]] &&
	    [_lastURL port] == [URL port]) {
		/*

		 * Set _socket to nil, so that in case of an error it won't be
		 * reused. If everything is successfull, we set _socket again
		 * at the end.
		 */
		socket = [_socket autorelease];
		_socket = nil;

		[_lastURL release];
		_lastURL = nil;

		/* Throw away content that has not been read yet */
		while (![_lastReply isAtEndOfStream]) {
			char buffer[512];

			[_lastReply readIntoBuffer: buffer
					    length: 512];
		}

		[_lastReply release];
		_lastReply = nil;
	} else
		socket = [self OF_createSocketForRequest: request];

	if (requestType == OF_HTTP_REQUEST_TYPE_GET)
		type = "GET";
	if (requestType == OF_HTTP_REQUEST_TYPE_HEAD)
		type = "HEAD";
	if (requestType == OF_HTTP_REQUEST_TYPE_POST)
		type = "POST";

	if ([(path = [URL path]) length] == 0)
		path = @"/";

	/*
	 * 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.
	 */
	if ([URL query] != nil)
		requestString = [OFMutableString stringWithFormat:
		    @"%s %@?%@ HTTP/%@\r\n",
		    type, path, [URL query], [request protocolVersionString]];
	else
		requestString = [OFMutableString stringWithFormat:
		    @"%s %@ HTTP/%@\r\n",
		    type, path, [request protocolVersionString]];

	if ([URL port] == 80)
		[requestString appendFormat: @"Host: %@\r\n", [URL host]];
	else
		[requestString appendFormat: @"Host: %@:%d\r\n", [URL host],
		    [URL port]];



	if ([headers objectForKey: @"User-Agent"] == nil)
		[requestString appendString:
		    @"User-Agent: Something using ObjFW "
		    @"<https://webkeks.org/objfw>\r\n"];

	keyEnumerator = [headers keyEnumerator];
	objectEnumerator = [headers objectEnumerator];

	while ((key = [keyEnumerator nextObject]) != nil &&
	    (object = [objectEnumerator nextObject]) != nil)
		[requestString appendFormat: @"%@: %@\r\n", key, object];

	if (requestType == OF_HTTP_REQUEST_TYPE_POST) {
		OFString *contentType = [request MIMEType];

		if (contentType == nil)
			contentType = @"application/x-www-form-urlencoded; "
			    @"charset=UTF-8\r\n";

		[requestString appendFormat:
		    @"Content-Type: %@\r\n"
		    @"Content-Length: %d\r\n",
		    contentType, [POSTData count] * [POSTData itemSize]];
	}

	if ([request protocolVersion].major == 1 &&
	    [request protocolVersion].minor == 0)
		[requestString appendString: @"Connection: keep-alive\r\n"];

	[requestString appendString: @"\r\n"];

	@try {
		[socket writeString: requestString];
	} @catch (OFWriteFailedException *e) {
		/* Reconnect in case a keep-alive connection timed out */
		socket = [self OF_createSocketForRequest: request];
		[socket writeString: requestString];
	} @catch (OFNotConnectedException *e) {
		/* Reconnect in case a keep-alive connection timed out */
		socket = [self OF_createSocketForRequest: request];
		[socket writeString: requestString];
	}

	if (requestType == OF_HTTP_REQUEST_TYPE_POST)
		[socket writeBuffer: [POSTData items]
			     length: [POSTData count] * [POSTData itemSize]];

	@try {
		line = [socket readLine];
	} @catch (OFInvalidEncodingException *e) {
		@throw [OFInvalidServerReplyException
		    exceptionWithClass: [self class]];
	}

	/*
	 * It's possible that the write succeeds on a connection that is
	 * keep-alive, but the connection has already been closed by the remote
	 * end due to a timeout. In this case, we need to reconnect.
	 */
	if (line == nil) {
		socket = [self OF_createSocketForRequest: request];
		[socket writeString: requestString];

		if (requestType == OF_HTTP_REQUEST_TYPE_POST)
			[socket writeBuffer: [POSTData items]
				     length: [POSTData count] *
					     [POSTData itemSize]];

		@try {
			line = [socket readLine];
		} @catch (OFInvalidEncodingException *e) {
			@throw [OFInvalidServerReplyException
			    exceptionWithClass: [self class]];
		}
	}

	if (![line hasPrefix: @"HTTP/"] || [line characterAtIndex: 8] != ' ')
		@throw [OFInvalidServerReplyException
		    exceptionWithClass: [self class]];

	version = [line substringWithRange: of_range(5, 3)];
	if (![version isEqual: @"1.0"] && ![version isEqual: @"1.1"])
383
384
385
386
387
388
389





























390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419

		do {
			tmp++;
		} while (*tmp == ' ');

		value = [OFString stringWithUTF8String: tmp];






























		if ((redirects > 0 && (status == 301 || status == 302 ||
		    status == 303 || status == 307) &&
		    [key isEqual: @"Location"]) && (_insecureRedirectsAllowed ||
		    [scheme isEqual: @"http"] ||
		    ![value hasPrefix: @"http://"])) {
			OFURL *newURL;
			OFHTTPRequest *newRequest;
			bool follow = true;

			newURL = [OFURL URLWithString: value
					relativeToURL: URL];

			if ([_delegate respondsToSelector:
			    @selector(client:shouldFollowRedirect:request:)])
				follow = [_delegate client: self
				      shouldFollowRedirect: newURL
						   request: request];

			if (!follow) {
				[serverHeaders setObject: value
						  forKey: key];
				continue;
			}

			newRequest = [OFHTTPRequest requestWithURL: newURL];
			[newRequest setRequestType: requestType];
			[newRequest setHeaders: headers];
			[newRequest setPOSTData: POSTData];
			[newRequest setMIMEType: [request MIMEType]];








>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
|
|
<
|

|
|

|
|
|
|
|

|
<
<
|
<







545
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
576
577
578
579
580
581
582
583
584
585
586

587
588
589
590
591
592
593
594
595
596
597
598


599

600
601
602
603
604
605
606

		do {
			tmp++;
		} while (*tmp == ' ');

		value = [OFString stringWithUTF8String: tmp];

		[serverHeaders setObject: value
				  forKey: key];
	}

	[serverHeaders makeImmutable];

	if ([_delegate respondsToSelector:
	    @selector(client:didReceiveHeaders:statusCode:request:)])
		[_delegate     client: self
		    didReceiveHeaders: serverHeaders
			   statusCode: status
			      request: request];

	reply = [[[OFHTTPClientReply alloc] initWithSocket: socket]
	    autorelease];
	[reply setProtocolVersionFromString: version];
	[reply setStatusCode: status];
	[reply setHeaders: serverHeaders];

	keepAlive = [serverHeaders objectForKey: @"Connection"];
	if ([version isEqual: @"1.1"] ||
	    (keepAlive != nil && [keepAlive isEqual: @"keep-alive"])) {
		[reply setKeepAlive: true];

		_socket = [socket retain];
		_lastURL = [URL copy];
		_lastReply = [reply retain];
	}

	if (redirects > 0 && (status == 301 || status == 302 ||
	    status == 303 || status == 307) &&
	    (redirect = [serverHeaders objectForKey: @"Location"]) != nil &&
	    (_insecureRedirectsAllowed || [scheme isEqual: @"http"] ||
	    ![redirect hasPrefix: @"http://"])) {
		OFURL *newURL;

		bool follow = true;

		newURL = [OFURL URLWithString: redirect
				relativeToURL: URL];

		if ([_delegate respondsToSelector:
		    @selector(client:shouldFollowRedirect:request:)])
			follow = [_delegate client: self
			      shouldFollowRedirect: newURL
					   request: request];

		if (follow) {


			OFHTTPRequest *newRequest;


			newRequest = [OFHTTPRequest requestWithURL: newURL];
			[newRequest setRequestType: requestType];
			[newRequest setHeaders: headers];
			[newRequest setPOSTData: POSTData];
			[newRequest setMIMEType: [request MIMEType]];

427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464












465
			[newRequest retain];
			objc_autoreleasePoolPop(pool);
			[newRequest autorelease];

			return [self performRequest: newRequest
					  redirects: redirects - 1];
		}

		[serverHeaders setObject: value
				  forKey: key];
	}

	[serverHeaders makeImmutable];

	if ([_delegate respondsToSelector:
	    @selector(client:didReceiveHeaders:statusCode:request:)])
		[_delegate     client: self
		    didReceiveHeaders: serverHeaders
			   statusCode: status
			      request: request];

	reply = [[OFHTTPClientReply alloc] initWithSocket: socket];
	[reply setProtocolVersionFromString: version];
	[reply setStatusCode: status];
	[reply setHeaders: serverHeaders];

	objc_autoreleasePoolPop(pool);

	[reply autorelease];

	if (status / 100 != 2)
		@throw [OFHTTPRequestFailedException
		    exceptionWithClass: [self class]
			       request: request
				 reply: reply];

	return reply;
}












@end







|
<
<
|
|
<
<
<
<
<
<
<
<
<
<
<
<
<
<

<










>
>
>
>
>
>
>
>
>
>
>
>

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
			[newRequest retain];
			objc_autoreleasePoolPop(pool);
			[newRequest autorelease];

			return [self performRequest: newRequest
					  redirects: redirects - 1];
		}
	}



	[reply retain];














	objc_autoreleasePoolPop(pool);

	[reply autorelease];

	if (status / 100 != 2)
		@throw [OFHTTPRequestFailedException
		    exceptionWithClass: [self class]
			       request: request
				 reply: reply];

	return reply;
}

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

	[_lastURL release];
	_lastURL = nil;

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

Modified src/exceptions/OFInvalidServerReplyException.m from [ec481228e3] to [b835408d30].

19
20
21
22
23
24
25
26
27
28
#import "OFInvalidServerReplyException.h"
#import "OFString.h"

@implementation OFInvalidServerReplyException
- (OFString*)description
{
	return [OFString stringWithFormat:
	    @"Got an invalid reply from the server in class %@", _inClass];
}
@end







|


19
20
21
22
23
24
25
26
27
28
#import "OFInvalidServerReplyException.h"
#import "OFString.h"

@implementation OFInvalidServerReplyException
- (OFString*)description
{
	return [OFString stringWithFormat:
	    @"Got an invalid reply from the server in class %@!", _inClass];
}
@end

Modified tests/OFHTTPClientTests.m from [34e44ef284] to [7b9b64988c].

66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
	if (![[client readLine] isEqual: @"GET /foo HTTP/1.1"])
		assert(0);

	if (![[client readLine] isEqual:
	    [OFString stringWithFormat: @"Host: 127.0.0.1:%" @PRIu16, port]])
		assert(0);

	if (![[client readLine] isEqual: @"Connection: close"])
		assert(0);

	if (![[client readLine] hasPrefix: @"User-Agent:"])
		assert(0);

	if (![[client readLine] isEqual: @""])
		assert(0);

	[client writeString: @"HTTP/1.1 200 OK\r\n"
			     @"cONTeNT-lENgTH: 7\r\n"
			     @"\r\n"
			     @"foo\n"
			     @"bar"];
	[client close];

	return nil;







<
<
<






|







66
67
68
69
70
71
72



73
74
75
76
77
78
79
80
81
82
83
84
85
86
	if (![[client readLine] isEqual: @"GET /foo HTTP/1.1"])
		assert(0);

	if (![[client readLine] isEqual:
	    [OFString stringWithFormat: @"Host: 127.0.0.1:%" @PRIu16, port]])
		assert(0);




	if (![[client readLine] hasPrefix: @"User-Agent:"])
		assert(0);

	if (![[client readLine] isEqual: @""])
		assert(0);

	[client writeString: @"HTTP/1.0 200 OK\r\n"
			     @"cONTeNT-lENgTH: 7\r\n"
			     @"\r\n"
			     @"foo\n"
			     @"bar"];
	[client close];

	return nil;