ObjFW  Check-in [2b7a70e246]

Overview
Comment:Split OFHTTPRequest into OFHTTP{Client,Request}.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 2b7a70e246ee4edbd64381cb41a3d76ce3d18c046bceb4d24ff8b2546357ed93
User & Date: js on 2012-12-09 12:13:28
Other Links: manifest | tags
Context
2012-12-09
12:31
Fix +[OFString stringWithUTF8StringNoCopy:…]. check-in: b55b4ab87b user: js tags: trunk
12:13
Split OFHTTPRequest into OFHTTP{Client,Request}. check-in: 2b7a70e246 user: js tags: trunk
2012-12-07
14:50
quicksort: Reduce used space. check-in: a747ad5478 user: js tags: trunk
Changes

Modified configure.ac from [22d4536db7] to [9c5224dec5].

458
459
460
461
462
463
464
465

466
467
468
469
470
471
472
458
459
460
461
462
463
464

465
466
467
468
469
470
471
472







-
+







		OFThreadPool.m		\
		OFTLSKey.m		\
		OFMutex.m		\
		OFRecursiveMutex.m	\
		OFCondition.m		\
	")
	AC_SUBST(OFTHREADTESTS_M, "OFThreadTests.m")
	AC_SUBST(OFHTTPREQUESTTESTS_M, "OFHTTPRequestTests.m")
	AC_SUBST(OFHTTPCLIENTTESTS_M, "OFHTTPClientTests.m")
	AC_SUBST(THREADING_H, "threading.h")

	AC_MSG_CHECKING(whether __thread works)
	AC_TRY_LINK([
		/* It seems __thread is buggy with GCC 4.1 */
		#if __GNUC__ == 4 && __GNUC_MINOR__ < 2
		# error buggy

Modified extra.mk.in from [e832178377] to [05e2d217e1].

20
21
22
23
24
25
26
27

28
29
30
31
32
33
34
20
21
22
23
24
25
26

27
28
29
30
31
32
33
34







-
+







EXCEPTIONS_LIB_A = @EXCEPTIONS_LIB_A@
FOUNDATION_COMPAT_M = @FOUNDATION_COMPAT_M@
INSTANCE_M = @INSTANCE_M@
LOOKUP_S = @LOOKUP_S@
OFBLOCKTESTS_M = @OFBLOCKTESTS_M@
OBJC_PROPERTIES_M = @OBJC_PROPERTIES_M@
OBJC_SYNC_M = @OBJC_SYNC_M@
OFHTTPREQUESTTESTS_M = @OFHTTPREQUESTTESTS_M@
OFHTTPCLIENTTESTS_M = @OFHTTPCLIENTTESTS_M@
OFPLUGIN_M = @OFPLUGIN_M@
OFPLUGINTESTS_M = @OFPLUGINTESTS_M@
OFSTREAMOBSERVER_KQUEUE_M = @OFSTREAMOBSERVER_KQUEUE_M@
OFSTREAMOBSERVER_POLL_M = @OFSTREAMOBSERVER_POLL_M@
OFSTREAMOBSERVER_SELECT_M = @OFSTREAMOBSERVER_SELECT_M@
OFTHREADTESTS_M = @OFTHREADTESTS_M@
PROPERTIESTESTS_M = @PROPERTIESTESTS_M@

Modified src/Makefile from [58bf8766a3] to [c26c9d673c].

17
18
19
20
21
22
23

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







+







       OFDataArray.m			\
       OFDataArray+Hashing.m		\
       OFDate.m				\
       OFDictionary.m			\
       OFEnumerator.m			\
       OFFile.m				\
       OFHash.m				\
       OFHTTPClient.m			\
       OFHTTPRequest.m			\
       OFIntrospection.m		\
       OFList.m				\
       OFMapTable.m			\
       OFMD5Hash.m			\
       OFMutableArray.m			\
       OFMutableDictionary.m		\

Modified src/OFDataArray.m from [0d3563afe7] to [ca2bfba7fb].

20
21
22
23
24
25
26

27
28
29
30
31
32
33
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34







+







#include <string.h>
#include <limits.h>

#import "OFDataArray.h"
#import "OFString.h"
#import "OFFile.h"
#import "OFURL.h"
#import "OFHTTPClient.h"
#import "OFHTTPRequest.h"
#import "OFXMLElement.h"

#import "OFHTTPRequestFailedException.h"
#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFNotImplementedException.h"
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
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







+















+

-
+








	return self;
}

- initWithContentsOfURL: (OFURL*)URL
{
	void *pool;
	OFHTTPClient *client;
	OFHTTPRequest *request;
	OFHTTPRequestResult *result;
	Class c;

	c = [self class];
	[self release];

	pool = objc_autoreleasePoolPush();

	if ([[URL scheme] isEqual: @"file"]) {
		self = [[c alloc] initWithContentsOfFile: [URL path]];
		objc_autoreleasePoolPop(pool);
		return self;
	}

	client = [OFHTTPClient client];
	request = [OFHTTPRequest requestWithURL: URL];
	result = [request perform];
	result = [client performRequest: request];

	if ([result statusCode] != 200)
		@throw [OFHTTPRequestFailedException
		    exceptionWithClass: [request class]
			       request: request
				result: result];

Added src/OFHTTPClient.h version [c649e6c2e2].



































































































































































































1
2
3
4
5
6
7
8
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
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#import "OFObject.h"

@class OFHTTPClient;
@class OFHTTPRequest;
@class OFHTTPRequestResult;
@class OFURL;
@class OFTCPSocket;
@class OFDictionary;
@class OFDataArray;

/*!
 * @brief A delegate for OFHTTPClient.
 */
#ifndef OF_HTTP_CLIENT_M
@protocol OFHTTPClientDelegate <OFObject>
#else
@protocol OFHTTPClientDelegate
#endif
#ifdef OF_HAVE_OPTIONAL_PROTOCOLS
@optional
#endif
/*!
 * @brief A callback which is called when an OFHTTPClient creates a socket.
 *
 * This is useful if the connection is using HTTPS and the server requires a
 * client certificate. This callback can then be used to tell the TLS socket
 * about the certificate. Another use case is to tell the socket about a SOCKS5
 * proxy it should use for this connection.
 *
 * @param client The OFHTTPClient that created a socket
 * @param socket The socket created by the OFHTTPClient
 * @param request The request for which the socket was created
 */
-    (void)client: (OFHTTPClient*)client
  didCreateSocket: (OFTCPSocket*)socket
	  request: (OFHTTPRequest*)request;

/*!
 * @brief A callback which is called when an OFHTTPClient received headers.
 *
 * @param client The OFHTTPClient which received the headers
 * @param headers The headers received
 * @param statusCode The status code received
 * @param request The request for which the headers and status code have been
 *		  received
 */
-      (void)client: (OFHTTPClient*)client
  didReceiveHeaders: (OFDictionary*)headers
	 statusCode: (int)statusCode
	    request: (OFHTTPRequest*)request;

/*!
 * @brief A callback which is called when an OFHTTPClient received data.
 *
 * This is useful for example if you want to update a status display.
 *
 * @param client The OFHTTPClient which received data
 * @param data The data the OFHTTPClient received
 * @param length The length of the data received, in bytes
 * @param request The request for which data has been received
 */
-   (void)client: (OFHTTPClient*)client
  didReceiveData: (const char*)data
	  length: (size_t)length
	 request: (OFHTTPRequest*)request;

/*!
 * @brief A callback which is called when an OFHTTPClient will follow a
 *	  redirect.
 *
 * If you want to get the headers and data for each redirect, set the number of
 * redirects to 0 and perform a new OFHTTPClient for each redirect. However,
 * this callback will not be called then and you have to look at the status code
 * to detect a redirect.
 *
 * This callback will only be called if the OFHTTPClient will follow a
 * redirect. If the maximum number of redirects has been reached already, this
 * callback will not be called.
 *
 * @param client The OFHTTPClient which wants to follow a redirect
 * @param URL The URL to which it will follow a redirect
 * @param request The request for which the OFHTTPClient wants to redirect
 * @return A boolean whether the OFHTTPClient should follow the redirect
 */
-	  (BOOL)client: (OFHTTPClient*)client
  shouldFollowRedirect: (OFURL*)URL
	       request: (OFHTTPRequest*)request;
@end

/*!
 * @brief A class for performing HTTP requests.
 */
@interface OFHTTPClient: OFObject
{
	id <OFHTTPClientDelegate> delegate;
	BOOL storesData;
	BOOL insecureRedirectsAllowed;
}

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

/*!
 * @brief Creates a new OFHTTPClient.
 *
 * @return A new, autoreleased OFHTTPClient
 */
+ (instancetype)client;

/*!
 * @brief Sets the delegate of the HTTP request.
 *
 * @param delegate The delegate of the HTTP request
 */
- (void)setDelegate: (id <OFHTTPClientDelegate>)delegate;

/*!
 * @brief Returns the delegate of the HTTP reqeust.
 *
 * @return The delegate of the HTTP request
 */
- (id <OFHTTPClientDelegate>)delegate;

/*!
 * @brief Sets whether redirects from HTTPS to HTTP are allowed.
 *
 * @param allowed Whether redirects from HTTPS to HTTP are allowed
 */
- (void)setInsecureRedirectsAllowed: (BOOL)allowed;

/*!
 * @brief Returns whether redirects from HTTPS to HTTP will be allowed
 *
 * @return Whether redirects from HTTPS to HTTP will be allowed
 */
- (BOOL)insecureRedirectsAllowed;

/*!
 * @brief Sets whether an OFDataArray with the data should be created.
 *
 * Setting this to NO is only useful if you are using the delegate to handle the
 * data.
 *
 * @param enabled Whether to store the data in an OFDataArray
 */
- (void)setStoresData: (BOOL)enabled;

/*!
 * @brief Returns whether an OFDataArray with the date should be created.
 *
 * @return Whether an OFDataArray with the data should be created
 */
- (BOOL)storesData;

/*!
 * @brief Performs the specified HTTP request
 */
- (OFHTTPRequestResult*)performRequest: (OFHTTPRequest*)request;

/*!
 * @brief Performs the HTTP request and returns an OFHTTPRequestResult.
 *
 * @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 OFHTTPRequestResult
 * @return An OFHTTPRequestResult with the result of the HTTP request
 */
- (OFHTTPRequestResult*)performRequest: (OFHTTPRequest*)request
			     redirects: (size_t)redirects;
@end

@interface OFObject (OFHTTPClientDelegate) <OFHTTPClientDelegate>
@end

extern Class of_http_client_tls_socket_class;

Added src/OFHTTPClient.m version [6940cfd80f].




























































































































































































































































































































































































































































































































1
2
3
4
5
6
7
8
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
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
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
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
496
497
498
499
500
501
502
503
504
505
506
507
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#define OF_HTTP_REQUEST_M

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

#import "OFHTTPClient.h"
#import "OFHTTPRequest.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 "OFOutOfRangeException.h"
#import "OFTruncatedDataException.h"
#import "OFUnsupportedProtocolException.h"
#import "OFUnsupportedVersionException.h"

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

Class of_http_client_tls_socket_class = Nil;

static OF_INLINE void
normalizeKey(OFString *key)
{
	uint8_t *str = (uint8_t*)[key UTF8String];
	BOOL firstLetter = YES;

	while (*str != '\0') {
		if (!isalnum(*str)) {
			firstLetter = YES;
			str++;
			continue;
		}

		*str = (firstLetter ? toupper(*str) : tolower(*str));

		firstLetter = NO;
		str++;
	}
}

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

- init
{
	self = [super init];

	storesData = YES;

	return self;
}

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

- (id <OFHTTPClientDelegate>)delegate
{
	return delegate;
}

- (void)setInsecureRedirectsAllowed: (BOOL)allowed
{
	insecureRedirectsAllowed = allowed;
}

- (BOOL)insecureRedirectsAllowed
{
	return insecureRedirectsAllowed;
}

- (void)setStoresData: (BOOL)storesData_
{
	storesData = storesData_;
}

- (BOOL)storesData
{
	return storesData;
}

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

- (OFHTTPRequestResult*)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 *sock;
	OFHTTPRequestResult *result;
	OFString *line, *path, *version;
	OFMutableDictionary *serverHeaders;
	OFDataArray *data;
	OFEnumerator *keyEnumerator, *objectEnumerator;
	OFString *key, *object, *contentLengthHeader;
	int status;
	const char *type = NULL;
	size_t contentLength = 0;
	BOOL chunked;
	char *buffer;
	size_t bytesReceived;

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

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

		sock = [[[of_http_client_tls_socket_class alloc] init]
		    autorelease];
	}

	[delegate client: self
	 didCreateSocket: sock
		 request: request];

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

	/*
	 * Work around a bug with packet bisection in lighttpd when using
	 * HTTPS.
	 */
	[sock setWriteBufferEnabled: YES];

	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]) isEqual: @""])
		path = @"/";

	if ([URL query] != nil)
		[sock writeFormat: @"%s %@?%@ HTTP/1.1\r\n",
		    type, path, [URL query]];
	else
		[sock writeFormat: @"%s %@ HTTP/1.1\r\n", type, path];

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

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

	if ([headers objectForKey: @"User-Agent"] == nil)
		[sock 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)
		[sock 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";

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

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

	/* Work around a bug in lighttpd, see above */
	[sock flushWriteBuffer];
	[sock setWriteBufferEnabled: NO];

	if (requestType == OF_HTTP_REQUEST_TYPE_POST)
		[sock writeBuffer: [postData cArray]
			   length: [postData count] * [postData itemSize]];

	@try {
		line = [sock 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"])
		@throw [OFUnsupportedVersionException
		    exceptionWithClass: [self class]
			       version: version];

	status = (int)[[line substringWithRange: of_range(9, 3)] decimalValue];

	serverHeaders = [OFMutableDictionary dictionary];

	for (;;) {
		OFString *key, *value;
		const char *line_c, *tmp;

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

		if (line == nil)
			@throw [OFInvalidServerReplyException
			    exceptionWithClass: [self class]];

		if ([line isEqual: @""])
			break;

		line_c = [line UTF8String];

		if ((tmp = strchr(line_c, ':')) == NULL)
			@throw [OFInvalidServerReplyException
			    exceptionWithClass: [self class]];

		key = [OFString stringWithUTF8String: line_c
					      length: tmp - line_c];
		normalizeKey(key);

		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;

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

			follow = [delegate client: self
			     shouldFollowRedirect: newURL
					  request: request];

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

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

			if (status == 303) {
				[newRequest
				    setRequestType: OF_HTTP_REQUEST_TYPE_GET];
				[newRequest setPostData: nil];
				[newRequest setMIMEType: nil];
			}

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

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

		[serverHeaders setObject: value
				  forKey: key];
	}

	[delegate      client: self
	    didReceiveHeaders: serverHeaders
		   statusCode: status
		      request: request];

	data = (storesData ? [OFDataArray dataArray] : nil);
	chunked = [[serverHeaders objectForKey: @"Transfer-Encoding"]
	    isEqual: @"chunked"];

	contentLengthHeader = [serverHeaders objectForKey: @"Content-Length"];

	if (contentLengthHeader != nil) {
		contentLength = (size_t)[contentLengthHeader decimalValue];

		if (contentLength > SIZE_MAX)
			@throw [OFOutOfRangeException
			    exceptionWithClass: [self class]];
	}

	buffer = [self allocMemoryWithSize: of_pagesize];
	bytesReceived = 0;
	@try {
		if (chunked) {
			for (;;) {
				void *pool2 = objc_autoreleasePoolPush();
				size_t toRead;
				of_range_t range;

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

				range = [line rangeOfString: @";"];
				if (range.location != OF_NOT_FOUND)
					line = [line substringWithRange:
					    of_range(0, range.location)];

				@try {
					toRead =
					    (size_t)[line hexadecimalValue];
				} @catch (OFInvalidFormatException *e) {
					@throw [OFInvalidServerReplyException
					    exceptionWithClass: [self class]];
				}

				if (toRead == 0 ||
				    (contentLengthHeader != nil &&
				    contentLength >= bytesReceived))
					break;

				while (toRead > 0) {
					size_t length = (toRead < of_pagesize
					    ? toRead : of_pagesize);

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

					[delegate client: self
					  didReceiveData: buffer
						  length: length
						 request: request];

					objc_autoreleasePoolPop(pool2);
					pool2 = objc_autoreleasePoolPush();

					bytesReceived += length;
					[data addItemsFromCArray: buffer
							   count: length];

					toRead -= length;
				}

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

				if (![line isEqual: @""])
					@throw [OFInvalidServerReplyException
					    exceptionWithClass: [self class]];

				objc_autoreleasePoolPop(pool2);
			}
		} else {
			size_t length;

			while (![sock isAtEndOfStream]) {
				void *pool2;

				length = [sock readIntoBuffer: buffer
						       length: of_pagesize];

				pool2 = objc_autoreleasePoolPush();

				[delegate client: self
				  didReceiveData: buffer
					  length: length
					 request: request];

				objc_autoreleasePoolPop(pool2);

				bytesReceived += length;
				[data addItemsFromCArray: buffer
						   count: length];

				if (contentLengthHeader != nil &&
				    bytesReceived >= contentLength)
					break;
			}
		}
	} @finally {
		[self freeMemory: buffer];
	}

	[sock close];

	/*
	 * We only want to throw on status code 200 as we will throw an
	 * OFHTTPRequestFailedException for all other status codes later.
	 */
	if (status == 200 && contentLengthHeader != nil &&
	    contentLength != bytesReceived)
		@throw [OFTruncatedDataException
		    exceptionWithClass: [self class]];

	[serverHeaders makeImmutable];

	result = [[OFHTTPRequestResult alloc]
	    OF_initWithStatusCode: status
			  headers: serverHeaders
			     data: data];

	objc_autoreleasePoolPop(pool);

	[result autorelease];

	if (status != 200)
		@throw [OFHTTPRequestFailedException
		    exceptionWithClass: [self class]
			       request: request
				result: result];

	return result;
}
@end

@implementation OFObject (OFHTTPClientDelegate)
-    (void)client: (OFHTTPClient*)client
  didCreateSocket: (OFTCPSocket*)socket
	  request: (OFHTTPRequest*)request
{
}

-      (void)client: (OFHTTPClient*)client
  didReceiveHeaders: (OFDictionary*)headers
	 statusCode: (int)statusCode
	    request: (OFHTTPRequest*)request
{
}

-   (void)client: (OFHTTPClient*)client
  didReceiveData: (const char*)data
	  length: (size_t)length
	 request: (OFHTTPRequest*)request
{
}

-	  (BOOL)client: (OFHTTPClient*)client
  shouldFollowRedirect: (OFURL*)URL
	       request: (OFHTTPRequest*)request
{
	return YES;
}
@end

Modified src/OFHTTPRequest.h from [269d67ae36] to [a7837b8f3d].

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







-
+

-
+
-
-
-
+
-








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





-

-
-
-
+
+





-

-
+
-
-
+







 * 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 "OFObject.h"

@class OFString;
@class OFURL;
@class OFDictionary;
@class OFURL;
@class OFDataArray;
@class OFHTTPRequest;
@class OFHTTPRequestResult;
@class OFTCPSocket;
@class OFString;
@class OFDataArray;

typedef enum of_http_request_type_t {
	OF_HTTP_REQUEST_TYPE_GET,
	OF_HTTP_REQUEST_TYPE_POST,
	OF_HTTP_REQUEST_TYPE_HEAD
} of_http_request_type_t;

/*!
 * @brief A delegate for OFHTTPRequests.
 */
#ifndef OF_HTTP_REQUEST_M
@protocol OFHTTPRequestDelegate <OFObject>
#else
@protocol OFHTTPRequestDelegate
#endif
#ifdef OF_HAVE_OPTIONAL_PROTOCOLS
@optional
#endif
/*!
 * @brief A callback which is called when an OFHTTPRequest creates a socket.
 *
 * This is useful if the connection is using HTTPS and the server requires a
 * client certificate. This callback can then be used to tell the TLS socket
 * about the certificate. Another use case is to tell the socket about a SOCKS5
 * proxy it should use for this connection.
 *
 * @param request The OFHTTPRequest that created a socket
 * @param socket The socket created by the OFHTTPRequest
 */
-   (void)request: (OFHTTPRequest*)request
  didCreateSocket: (OFTCPSocket*)socket;

/*!
 * @brief A callback which is called when an OFHTTPRequest received headers.
 *
 * @param request The OFHTTPRequest which received the headers
 * @param headers The headers received
 * @param statusCode The status code received
 */
-     (void)request: (OFHTTPRequest*)request
  didReceiveHeaders: (OFDictionary*)headers
     withStatusCode: (int)statusCode;

/*!
 * @brief A callback which is called when an OFHTTPRequest received data.
 *
 * This is useful for example if you want to update a status display.
 *
 * @param request The OFHTTPRequest which received data
 * @param data The data the OFHTTPRequest received
 * @param length The length of the data received, in bytes
 */
-  (void)request: (OFHTTPRequest*)request
  didReceiveData: (const char*)data
      withLength: (size_t)length;

/*!
 * @brief A callback which is called when an OFHTTPRequest will follow a
 *	  redirect.
 *
 * If you want to get the headers and data for each redirect, set the number of
 * redirects to 0 and perform a new OFHTTPRequest for each redirect. However,
 * this callback will not be called then and you have to look at the status code
 * to detect a redirect.
 *
 * This callback will only be called if the OFHTTPRequest will follow a
 * redirect. If the maximum number of redirects has been reached already, this
 * callback will not be called.
 *
 * @param request The OFHTTPRequest which will follow a redirect
 * @param URL The URL to which it will follow a redirect
 * @return A boolean whether the OFHTTPRequest should follow the redirect
 */
-	   (BOOL)request: (OFHTTPRequest*)request
  shouldFollowRedirectTo: (OFURL*)URL;
@end

/*!
 * @brief A class for storing and performing HTTP requests.
 * @brief A class for storing HTTP requests.
 */
@interface OFHTTPRequest: OFObject
{
	OFURL *URL;
	of_http_request_type_t requestType;
	OFString *queryString;
	OFDictionary *headers;
	BOOL redirectsFromHTTPSToHTTPAllowed;
	id <OFHTTPRequestDelegate> delegate;
	BOOL storesData;
	OFDataArray *postData;
	OFString *MIMEType;
}

#ifdef OF_HAVE_PROPERTIES
@property (copy) OFURL *URL;
@property of_http_request_type_t requestType;
@property (copy) OFString *queryString;
@property (copy) OFDictionary *headers;
@property BOOL redirectsFromHTTPSToHTTPAllowed;
@property (retain) OFDataArray *postData;
@property (assign) id <OFHTTPRequestDelegate> delegate;
@property BOOL storesData;
@property (copy) OFString *MIMEType;
#endif

/*!
 * @brief Creates a new OFHTTPRequest.
 *
 * @return A new, autoreleased OFHTTPRequest
 */
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

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












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















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

-
+

-
+


-
+

-
+

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

-
+

-
+

-
-
+
-
-
-
+
-
-
-
-

-
+

-
-
-
-
+

-
+


















-
-
-
+
+
+


















-
-




-
-
-
-
-
/*!
 * @brief Returns the request type of the HTTP request.
 *
 * @return The request type of the HTTP request
 */
- (of_http_request_type_t)requestType;

/*!
 * @brief Sets the query string of the HTTP request.
 *
 * @param queryString The query string of the HTTP request
 */
- (void)setQueryString: (OFString*)queryString;

/*!
 * @brief Returns the query string of the HTTP request.
 *
 * @return The query string of the HTTP request
 */
- (OFString*)queryString;

/*!
 * @brief Sets a dictionary with headers for the HTTP request.
 *
 * @param headers A dictionary with headers for the HTTP request
 */
- (void)setHeaders: (OFDictionary*)headers;

/*!
 * @brief Retrusn a dictionary with headers for the HTTP request.
 *
 * @return A dictionary with headers for the HTTP request.
 */
- (OFDictionary*)headers;

/*!
 * @brief Sets whether redirects from HTTPS to HTTP are allowed.
 *
 * @param allowed Whether redirects from HTTPS to HTTP are allowed
 */
- (void)setRedirectsFromHTTPSToHTTPAllowed: (BOOL)allowed;

/*!
 * @brief Returns whether redirects from HTTPS to HTTP will be allowed
 *
 * @return Whether redirects from HTTPS to HTTP will be allowed
 */
- (BOOL)redirectsFromHTTPSToHTTPAllowed;

/*!
 * @brief Sets the delegate of the HTTP request.
 * @brief Sets the POST data of the HTTP request.
 *
 * @param delegate The delegate of the HTTP request
 * @param postData The POST data of the HTTP request
 */
- (void)setDelegate: (id <OFHTTPRequestDelegate>)delegate;
- (void)setPostData: (OFDataArray*)postData;

/*!
 * @brief Returns the delegate of the HTTP reqeust.
 * @brief Returns the POST data of the HTTP request.
 *
 * @return The delegate of the HTTP request
 * @return The POST data of the HTTP request
 */
- (id <OFHTTPRequestDelegate>)delegate;

- (OFDataArray*)postData;
/*!
 * @brief Sets whether an OFDataArray with the data should be created.
 *
 * Setting this to NO is only useful if you are using the delegate to handle the
 * data.
 *

 * @param storesData Whether to store the data in an OFDataArray
 */
- (void)setStoresData: (BOOL)storesData;

/*!
 * @brief Returns whether an OFDataArray with the date should be created.
 * @brief Sets the MIME type for the POST data.
 *
 * @return Whether an OFDataArray with the data should be created
 * @param MIMEType The MIME type for the POST data
 */
- (BOOL)storesData;

- (void)setMIMEType: (OFString*)MIMEType;
/*!
 * @brief Performs the HTTP request and returns an OFHTTPRequestResult.
 *

 * @return An OFHTTPRequestResult with the result of the HTTP request
 */
- (OFHTTPRequestResult*)perform;

/*!
 * @brief Performs the HTTP request and returns an OFHTTPRequestResult.
 * @brief Returns the MIME type for the POST data.
 *
 * @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 OFHTTPRequest
 * @return An OFHTTPRequestResult with the result of the HTTP request
 * @return The MIME type for the POST data
 */
- (OFHTTPRequestResult*)performWithRedirects: (size_t)redirects;
- (OFString*)MIMEType;
@end

/*!
 * @brief A class for storing the result of an HTTP request.
 */
@interface OFHTTPRequestResult: OFObject
{
	short statusCode;
	OFDataArray *data;
	OFDictionary *headers;
}

#ifdef OF_HAVE_PROPERTIES
@property (readonly) short statusCode;
@property (readonly, copy) OFDictionary *headers;
@property (readonly, retain) OFDataArray *data;
#endif

- initWithStatusCode: (short)status
	     headers: (OFDictionary*)headers
		data: (OFDataArray*)data;
- OF_initWithStatusCode: (short)status
		headers: (OFDictionary*)headers
		   data: (OFDataArray*)data;

/*!
 * @brief Returns the state code of the result of the HTTP request.
 *
 * @return The status code of the result of the HTTP request
 */
- (short)statusCode;

/*!
 * @brief Returns the headers of the result of the HTTP request.
 *
 * @return The headers of the result of the HTTP request
 */
- (OFDictionary*)headers;

/*!
 * @brief Returns the data received for the HTTP request.
 *
 * Returns nil if storesData was set to NO.
 *
 * @return The data received for the HTTP request
 */
- (OFDataArray*)data;
@end

@interface OFObject (OFHTTPRequestDelegate) <OFHTTPRequestDelegate>
@end

extern Class of_http_request_tls_socket_class;

Modified src/OFHTTPRequest.m from [21c09ca66b] to [3e1f9b1751].

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







-
-
-
-
-



-



-
-
-
-
-
-
-
-
-
-


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
















-
-
-
-
-







 * 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"

#define OF_HTTP_REQUEST_M

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

#import "OFHTTPRequest.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 "OFOutOfRangeException.h"
#import "OFTruncatedDataException.h"
#import "OFUnsupportedProtocolException.h"
#import "OFUnsupportedVersionException.h"

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

Class of_http_request_tls_socket_class = Nil;

static OF_INLINE void
normalizeKey(OFString *key)
{
	uint8_t *str = (uint8_t*)[key UTF8String];
	BOOL firstLetter = YES;

	while (*str != '\0') {
		if (!isalnum(*str)) {
			firstLetter = YES;
			str++;
			continue;
		}

		*str = (firstLetter ? toupper(*str) : tolower(*str));

		firstLetter = NO;
		str++;
	}
}

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

+ (instancetype)requestWithURL: (OFURL*)URL
{
	return [[[self alloc] initWithURL: URL] autorelease];
}

- init
{
	self = [super init];

	requestType = OF_HTTP_REQUEST_TYPE_GET;
	headers = [[OFDictionary alloc]
	    initWithObject: @"Something using ObjFW "
			    @"<https://webkeks.org/objfw/>"
		    forKey: @"User-Agent"];
	storesData = YES;

	return self;
}

- initWithURL: (OFURL*)URL_
{
	self = [self init];
100
101
102
103
104
105
106
107
108



109
110
111
112
113
114
115
57
58
59
60
61
62
63


64
65
66
67
68
69
70
71
72
73







-
-
+
+
+








	return self;
}

- (void)dealloc
{
	[URL release];
	[queryString release];
	[headers release];
	[headers release];
	[postData release];
	[MIMEType release];

	[super dealloc];
}

- (void)setURL: (OFURL*)URL_
{
	OF_SETTER(URL, URL_, YES, 1)
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
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
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
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539



540
541
542
543
544
545
546
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







-
-
-
-
-
-
-
-
-
-










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

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

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

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




-
-
-
+
+
+







}

- (of_http_request_type_t)requestType
{
	return requestType;
}

- (void)setQueryString: (OFString*)queryString_
{
	OF_SETTER(queryString, queryString_, YES, 1)
}

- (OFString*)queryString
{
	OF_GETTER(queryString, YES)
}

- (void)setHeaders: (OFDictionary*)headers_
{
	OF_SETTER(headers, headers_, YES, 1)
}

- (OFDictionary*)headers
{
	OF_GETTER(headers, YES)
}

- (void)setRedirectsFromHTTPSToHTTPAllowed: (BOOL)allowed
{
	redirectsFromHTTPSToHTTPAllowed = allowed;
}

- (BOOL)redirectsFromHTTPSToHTTPAllowed
{
	return redirectsFromHTTPSToHTTPAllowed;
}

- (void)setDelegate: (id <OFHTTPRequestDelegate>)delegate_
{
	delegate = delegate_;
}

- (id <OFHTTPRequestDelegate>)delegate
{
	return delegate;
}

- (void)setStoresData: (BOOL)storesData_
{
	storesData = storesData_;
}

- (BOOL)storesData
{
	return storesData;
}

- (OFHTTPRequestResult*)perform
{
	return [self performWithRedirects: 10];
}

- (OFHTTPRequestResult*)performWithRedirects: (size_t)redirects
{
	void *pool = objc_autoreleasePoolPush();
	OFString *scheme = [URL scheme];
	OFTCPSocket *sock;
	OFHTTPRequestResult *result;
	OFString *line, *path, *version;
	OFMutableDictionary *serverHeaders;
	OFDataArray *data;
- (void)setPostData: (OFDataArray*)postData_
	OFEnumerator *keyEnumerator, *objectEnumerator;
	OFString *key, *object, *contentLengthHeader;
	int status;
	const char *type = NULL;
	size_t contentLength = 0;
	BOOL chunked;
	char *buffer;
	size_t bytesReceived;

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

	OF_SETTER(postData, postData_, YES, 0)
	if ([scheme isEqual: @"http"])
		sock = [OFTCPSocket socket];
	else {
		if (of_http_request_tls_socket_class == Nil)
			@throw [OFUnsupportedProtocolException
			    exceptionWithClass: [self class]
					   URL: URL];

}
		sock = [[[of_http_request_tls_socket_class alloc] init]
		    autorelease];
	}

	[delegate request: self
	  didCreateSocket: sock];

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

	/*
	 * Work around a bug with packet bisection in lighttpd when using
	 * HTTPS.
	 */
	[sock setWriteBufferEnabled: YES];

	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]) isEqual: @""])
		path = @"/";

	if ([URL query] != nil)
		[sock writeFormat: @"%s %@?%@ HTTP/1.1\r\n",
		    type, path, [URL query]];
	else
		[sock writeFormat: @"%s %@ HTTP/1.1\r\n", type, path];

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

- (OFDataArray*)postData
	[sock writeString: @"Connection: close\r\n"];

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

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

}
	if (requestType == OF_HTTP_REQUEST_TYPE_POST) {
		if ([headers objectForKey: @"Content-Type"] == nil)
			[sock writeString: @"Content-Type: "
			    @"application/x-www-form-urlencoded; "
			    @"charset=UTF-8\r\n"];

		if ([headers objectForKey: @"Content-Length"] == nil)
			[sock writeFormat: @"Content-Length: %d\r\n",
			    [queryString UTF8StringLength]];
	}

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

	/* Work around a bug in lighttpd, see above */
	[sock flushWriteBuffer];
	[sock setWriteBufferEnabled: NO];

	if (requestType == OF_HTTP_REQUEST_TYPE_POST)
		[sock writeString: queryString];

	@try {
		line = [sock 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"])
		@throw [OFUnsupportedVersionException
		    exceptionWithClass: [self class]
			       version: version];

	status = (int)[[line substringWithRange: of_range(9, 3)] decimalValue];

	serverHeaders = [OFMutableDictionary dictionary];

	for (;;) {
		OFString *key, *value;
		const char *line_c, *tmp;

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

		if (line == nil)
			@throw [OFInvalidServerReplyException
			    exceptionWithClass: [self class]];

		if ([line isEqual: @""])
			break;

		line_c = [line UTF8String];

		if ((tmp = strchr(line_c, ':')) == NULL)
			@throw [OFInvalidServerReplyException
			    exceptionWithClass: [self class]];

		key = [OFString stringWithUTF8String: line_c
					      length: tmp - line_c];
		normalizeKey(key);

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

		value = [OFString stringWithUTF8String: tmp];

		if ((redirects > 0 && (status == 301 || status == 302 ||
		    status == 303 || status == 307) &&
		    [key isEqual: @"Location"]) &&
		    (redirectsFromHTTPSToHTTPAllowed ||
		    [scheme isEqual: @"http"] ||
		    ![value hasPrefix: @"http://"])) {
			OFURL *new;
			BOOL follow;

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

- (void)setMIMEType: (OFString*)MIMEType_
			follow = [delegate request: self
			    shouldFollowRedirectTo: new];

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

	OF_SETTER(MIMEType, MIMEType_, YES, 1)
			new = [new retain];
			[URL release];
			URL = new;

}
			if (status == 303) {
				requestType = OF_HTTP_REQUEST_TYPE_GET;
				[queryString release];
				queryString = nil;
			}

			objc_autoreleasePoolPop(pool);

			return [self performWithRedirects: redirects - 1];
		}

		[serverHeaders setObject: value
				  forKey: key];
	}

	[delegate request: self
	didReceiveHeaders: serverHeaders
	   withStatusCode: status];

	data = (storesData ? [OFDataArray dataArray] : nil);
	chunked = [[serverHeaders objectForKey: @"Transfer-Encoding"]
	    isEqual: @"chunked"];

	contentLengthHeader = [serverHeaders objectForKey: @"Content-Length"];

	if (contentLengthHeader != nil) {
		contentLength = (size_t)[contentLengthHeader decimalValue];

		if (contentLength > SIZE_MAX)
			@throw [OFOutOfRangeException
			    exceptionWithClass: [self class]];
	}

	buffer = [self allocMemoryWithSize: of_pagesize];
	bytesReceived = 0;
	@try {
		if (chunked) {
			for (;;) {
				void *pool2 = objc_autoreleasePoolPush();
				size_t toRead;
				of_range_t range;

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

				range = [line rangeOfString: @";"];
				if (range.location != OF_NOT_FOUND)
					line = [line substringWithRange:
					    of_range(0, range.location)];

				@try {
					toRead =
					    (size_t)[line hexadecimalValue];
				} @catch (OFInvalidFormatException *e) {
					@throw [OFInvalidServerReplyException
					    exceptionWithClass: [self class]];
				}

				if (toRead == 0 ||
				    (contentLengthHeader != nil &&
				    contentLength >= bytesReceived))
					break;

				while (toRead > 0) {
					size_t length = (toRead < of_pagesize
					    ? toRead : of_pagesize);

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

					[delegate request: self
					   didReceiveData: buffer
					       withLength: length];

- (OFString*)MIMEType
					objc_autoreleasePoolPop(pool2);
					pool2 = objc_autoreleasePoolPush();

{
					bytesReceived += length;
					[data addItemsFromCArray: buffer
							   count: length];

	OF_GETTER(MIMEType, YES)
					toRead -= length;
				}

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

				if (![line isEqual: @""])
					@throw [OFInvalidServerReplyException
					    exceptionWithClass: [self class]];

				objc_autoreleasePoolPop(pool2);
			}
		} else {
			size_t length;

			while (![sock isAtEndOfStream]) {
				void *pool2;

				length = [sock readIntoBuffer: buffer
						       length: of_pagesize];

				pool2 = objc_autoreleasePoolPush();

				[delegate request: self
				   didReceiveData: buffer
				       withLength: length];

				objc_autoreleasePoolPop(pool2);

				bytesReceived += length;
				[data addItemsFromCArray: buffer
						   count: length];

				if (contentLengthHeader != nil &&
				    bytesReceived >= contentLength)
					break;
			}
		}
	} @finally {
		[self freeMemory: buffer];
	}

	[sock close];

	/*
	 * We only want to throw on these status codes as we will throw an
	 * OFHTTPRequestFailedException for all other status codes later.
	 */
	if (contentLengthHeader != nil && contentLength != bytesReceived &&
	    (status == 200 || status == 301 || status == 302 || status == 303 ||
	    status == 307))
		@throw [OFTruncatedDataException
		    exceptionWithClass: [self class]];

	[serverHeaders makeImmutable];

	result = [[OFHTTPRequestResult alloc] initWithStatusCode: status
							 headers: serverHeaders
							    data: data];

	switch (status) {
	case 200:
	case 301:
	case 302:
	case 303:
	case 307:
		break;
	default:
		[result autorelease];
		@throw [OFHTTPRequestFailedException
		    exceptionWithClass: [self class]
			       request: self
				result: result];
	}

	objc_autoreleasePoolPop(pool);

	return [result autorelease];
}
@end

@implementation OFHTTPRequestResult
- initWithStatusCode: (short)status
	     headers: (OFDictionary*)headers_
		data: (OFDataArray*)data_
- OF_initWithStatusCode: (short)status
		headers: (OFDictionary*)headers_
		   data: (OFDataArray*)data_
{
	self = [super init];

	statusCode = status;
	data = [data_ retain];
	headers = [headers_ copy];

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
151
152
153
154
155
156
157
158

































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
	OF_GETTER(headers, YES)
}

- (OFDataArray*)data
{
	OF_GETTER(data, YES)
}
@end

@implementation OFObject (OFHTTPRequestDelegate)
-   (void)request: (OFHTTPRequest*)request
  didCreateSocket: (OFTCPSocket*)socket
{
}

-     (void)request: (OFHTTPRequest*)request
  didReceiveHeaders: (OFDictionary*)headers
     withStatusCode: (int)statusCode
{
}

-  (void)request: (OFHTTPRequest*)request
  didReceiveData: (const char*)data
      withLength: (size_t)len
{
}

-	   (BOOL)request: (OFHTTPRequest*)request
  shouldFollowRedirectTo: (OFURL*)url
{
	return YES;
}
@end

Modified src/OFMutex.m from [0fa68d097c] to [dc1f59fb34].

64
65
66
67
68
69
70
71

72
73
74
75
76
77
78
64
65
66
67
68
69
70

71
72
73
74
75
76
77
78







-
+







	if (!of_mutex_unlock(&mutex))
		@throw [OFUnlockFailedException exceptionWithClass: [self class]
							      lock: self];
}

- (void)setName: (OFString*)name_
{
	OF_SETTER(name, name_, YES, YES)
	OF_SETTER(name, name_, YES, 1)
}

- (OFString*)name
{
	OF_GETTER(name, YES)
}

Modified src/OFRecursiveMutex.m from [fe2de7b87a] to [959b4c16f6].

64
65
66
67
68
69
70
71

72
73
74
75
76
77
78
64
65
66
67
68
69
70

71
72
73
74
75
76
77
78







-
+







	if (!of_rmutex_unlock(&rmutex))
		@throw [OFUnlockFailedException exceptionWithClass: [self class]
							      lock: self];
}

- (void)setName: (OFString*)name_
{
	OF_SETTER(name, name_, YES, YES)
	OF_SETTER(name, name_, YES, 1)
}

- (OFString*)name
{
	OF_GETTER(name, YES)
}

Modified src/OFString.m from [f8021112b9] to [376b86841b].

24
25
26
27
28
29
30

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







+








#import "OFString.h"
#import "OFString_UTF8.h"
#import "OFArray.h"
#import "OFDictionary.h"
#import "OFFile.h"
#import "OFURL.h"
#import "OFHTTPClient.h"
#import "OFHTTPRequest.h"
#import "OFDataArray.h"
#import "OFXMLElement.h"

#import "OFHTTPRequestFailedException.h"
#import "OFInitializationFailedException.h"
#import "OFInvalidArgumentException.h"
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
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







+




















+

-
+







				  encoding: OF_STRING_ENCODING_AUTODETECT];
}

- initWithContentsOfURL: (OFURL*)URL
	       encoding: (of_string_encoding_t)encoding
{
	void *pool;
	OFHTTPClient *client;
	OFHTTPRequest *request;
	OFHTTPRequestResult *result;
	OFString *contentType;
	Class c;

	c = [self class];
	[self release];

	pool = objc_autoreleasePoolPush();

	if ([[URL scheme] isEqual: @"file"]) {
		if (encoding == OF_STRING_ENCODING_AUTODETECT)
			encoding = OF_STRING_ENCODING_UTF_8;

		self = [[c alloc] initWithContentsOfFile: [URL path]
						encoding: encoding];
		objc_autoreleasePoolPop(pool);
		return self;
	}

	client = [OFHTTPClient client];
	request = [OFHTTPRequest requestWithURL: URL];
	result = [request perform];
	result = [client performRequest: request];

	if ([result statusCode] != 200)
		@throw [OFHTTPRequestFailedException
		    exceptionWithClass: [request class]
			       request: request
				result: result];

Modified src/OFThread.m from [eb2ad7e37f] to [bdce062fba].

341
342
343
344
345
346
347
348

349
350
351
352
353
354
355
341
342
343
344
345
346
347

348
349
350
351
352
353
354
355







-
+







- (OFString*)name
{
	OF_GETTER(name, YES)
}

- (void)setName: (OFString*)name_
{
	OF_SETTER(name, name_, YES, YES)
	OF_SETTER(name, name_, YES, 1)

	if (running == OF_THREAD_RUNNING)
		set_thread_name(self);
}

- (void)dealloc
{

Modified src/ObjFW.h from [b47e6ec8d8] to [883aacb44f].

49
50
51
52
53
54
55

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







+







#import "OFStreamSocket.h"
#import "OFTCPSocket.h"
#import "OFTLSSocket.h"
#import "OFProcess.h"
#import "OFStreamObserver.h"

#import "OFHTTPRequest.h"
#import "OFHTTPClient.h"

#import "OFHash.h"
#import "OFMD5Hash.h"
#import "OFSHA1Hash.h"

#import "OFXMLAttribute.h"
#import "OFXMLElement.h"

Modified tests/Makefile from [733a9bd398] to [919fabd591].

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

13
14
15
16
17
18
19
1
2
3
4
5
6
7
8
9
10
11

12
13
14
15
16
17
18
19











-
+







include ../extra.mk

SUBDIRS = ${TESTPLUGIN}

PROG_NOINST = tests${PROG_SUFFIX}
SRCS = ForwardingTests.m		\
       OFArrayTests.m			\
       ${OFBLOCKTESTS_M}		\
       OFDataArrayTests.m		\
       OFDateTests.m			\
       OFDictionaryTests.m		\
       ${OFHTTPREQUESTTESTS_M}		\
       ${OFHTTPCLIENTTESTS_M}		\
       OFJSONTests.m			\
       OFListTests.m			\
       OFMD5HashTests.m			\
       OFNumberTests.m			\
       OFObjectTests.m			\
       ${OFPLUGINTESTS_M}		\
       OFSerializationTests.m		\

Added tests/OFHTTPClientTests.m version [f46357cbad].































































































































1
2
3
4
5
6
7
8
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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <assert.h>

#import "OFHTTPClient.h"
#import "OFHTTPRequest.h"
#import "OFString.h"
#import "OFTCPSocket.h"
#import "OFThread.h"
#import "OFCondition.h"
#import "OFURL.h"
#import "OFDictionary.h"
#import "OFAutoreleasePool.h"

#import "TestsAppDelegate.h"

static OFString *module = @"OFHTTPClient";
static OFCondition *cond;

@interface OFHTTPClientTestsServer: OFThread
{
@public
	uint16_t port;
}
@end

@implementation OFHTTPClientTestsServer
- main
{
	OFTCPSocket *listener, *client;

	[cond lock];

	listener = [OFTCPSocket socket];
	port = [listener bindToHost: @"127.0.0.1"
			       port: 0];
	[listener listen];

	[cond signal];
	[cond unlock];

	client = [listener accept];

	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;
}
@end

@implementation TestsAppDelegate (OFHTTPClientests)
- (void)HTTPClientTests
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
	OFHTTPClientTestsServer *server;
	OFURL *url;
	OFHTTPClient *client;
	OFHTTPRequest *req;
	OFHTTPRequestResult *res;

	cond = [OFCondition condition];
	[cond lock];

	server = [[[OFHTTPClientTestsServer alloc] init] autorelease];
	[server start];

	[cond wait];
	[cond unlock];

	url = [OFURL URLWithString:
	    [OFString stringWithFormat: @"http://127.0.0.1:%" @PRIu16 "/foo",
					server->port]];

	TEST(@"-[performRequest:]",
	    R(client = [OFHTTPClient client]) &&
	    R(req = [OFHTTPRequest requestWithURL: url]) &&
	    R(res = [client performRequest: req]))

	TEST(@"Normalization of server header keys",
	     ([[res headers] objectForKey: @"Content-Length"] != nil))

	[server join];

	[pool drain];
}
@end

Deleted tests/OFHTTPRequestTests.m version [9265df0702].

1
2
3
4
5
6
7
8
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



























































































































-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012
 *   Jonathan Schleifer <js@webkeks.org>
 *
 * All rights reserved.
 *
 * This file is part of ObjFW. It may be distributed under the terms of the
 * Q Public License 1.0, which can be found in the file LICENSE.QPL included in
 * the packaging of this file.
 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#include "config.h"

#include <stdlib.h>
#include <string.h>
#include <time.h>

#include <assert.h>

#import "OFHTTPRequest.h"
#import "OFString.h"
#import "OFTCPSocket.h"
#import "OFThread.h"
#import "OFCondition.h"
#import "OFURL.h"
#import "OFDictionary.h"
#import "OFAutoreleasePool.h"

#import "TestsAppDelegate.h"

static OFString *module = @"OFHTTPRequest";
static OFCondition *cond;

@interface OFHTTPRequestTestsServer: OFThread
{
@public
	uint16_t port;
}
@end

@implementation OFHTTPRequestTestsServer
- main
{
	OFTCPSocket *listener, *client;

	[cond lock];

	listener = [OFTCPSocket socket];
	port = [listener bindToHost: @"127.0.0.1"
			       port: 0];
	[listener listen];

	[cond signal];
	[cond unlock];

	client = [listener accept];

	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;
}
@end

@implementation TestsAppDelegate (OFHTTPRequesTests)
- (void)HTTPRequestTests
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
	OFHTTPRequestTestsServer *server;
	OFURL *url;
	OFHTTPRequest *req;
	OFHTTPRequestResult *res;

	cond = [OFCondition condition];
	[cond lock];

	server = [[[OFHTTPRequestTestsServer alloc] init] autorelease];
	[server start];

	[cond wait];
	[cond unlock];

	url = [OFURL URLWithString:
	    [OFString stringWithFormat: @"http://127.0.0.1:%" @PRIu16 "/foo",
					server->port]];

	TEST(@"+[requestWithURL]", (req = [OFHTTPRequest requestWithURL: url]))

	TEST(@"-[perform]", (res = [req perform]))

	TEST(@"Normalization of server header keys",
	     ([[res headers] objectForKey: @"Content-Length"] != nil))

	[server join];

	[pool drain];
}
@end

Modified tests/TestsAppDelegate.h from [bae42d2e23] to [45e8b9a744].

92
93
94
95
96
97
98
99
100


101
102
103
104
105
106
107
92
93
94
95
96
97
98


99
100
101
102
103
104
105
106
107







-
-
+
+







- (void)dictionaryTests;
@end

@interface TestsAppDelegate (ForwardingTests)
- (void)forwardingTests;
@end

@interface TestsAppDelegate (OFHTTPRequestTests)
- (void)HTTPRequestTests;
@interface TestsAppDelegate (OFHTTPClientTests)
- (void)HTTPClientTests;
@end

@interface TestsAppDelegate (OFJSONTests)
- (void)JSONTests;
@end

@interface TestsAppDelegate (OFListTests)

Modified tests/TestsAppDelegate.m from [cbb72ed2a0] to [97a3d3cea0].

137
138
139
140
141
142
143
144

145
146
147
148
149
150
151
137
138
139
140
141
142
143

144
145
146
147
148
149
150
151







-
+







	[self streamTests];
	[self TCPSocketTests];
#ifdef OF_THREADS
	[self threadTests];
#endif
	[self URLTests];
#ifdef OF_THREADS
	[self HTTPRequestTests];
	[self HTTPClientTests];
#endif
	[self XMLParserTests];
	[self XMLNodeTests];
	[self XMLElementBuilderTests];
	[self serializationTests];
	[self JSONTests];
#ifdef OF_PLUGINS