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
		OFThreadPool.m		\
		OFTLSKey.m		\
		OFMutex.m		\
		OFRecursiveMutex.m	\
		OFCondition.m		\
	")
	AC_SUBST(OFTHREADTESTS_M, "OFThreadTests.m")
	AC_SUBST(OFHTTPREQUESTTESTS_M, "OFHTTPRequestTests.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







|







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







|







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@
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
       OFDataArray.m			\
       OFDataArray+Hashing.m		\
       OFDate.m				\
       OFDictionary.m			\
       OFEnumerator.m			\
       OFFile.m				\
       OFHash.m				\

       OFHTTPRequest.m			\
       OFIntrospection.m		\
       OFList.m				\
       OFMapTable.m			\
       OFMD5Hash.m			\
       OFMutableArray.m			\
       OFMutableDictionary.m		\







>







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
#include <string.h>
#include <limits.h>

#import "OFDataArray.h"
#import "OFString.h"
#import "OFFile.h"
#import "OFURL.h"

#import "OFHTTPRequest.h"
#import "OFXMLElement.h"

#import "OFHTTPRequestFailedException.h"
#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFNotImplementedException.h"







>







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

	return self;
}

- initWithContentsOfURL: (OFURL*)URL
{
	void *pool;

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


	request = [OFHTTPRequest requestWithURL: URL];
	result = [request perform];

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








>















>

|







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 = [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
 * 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 OFDictionary;
@class OFURL;
@class OFHTTPRequest;
@class OFHTTPRequestResult;
@class OFTCPSocket;
@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.
 */
@interface OFHTTPRequest: OFObject
{
	OFURL *URL;
	of_http_request_type_t requestType;
	OFString *queryString;
	OFDictionary *headers;
	BOOL redirectsFromHTTPSToHTTPAllowed;
	id <OFHTTPRequestDelegate> delegate;
	BOOL storesData;

}

#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 (assign) id <OFHTTPRequestDelegate> delegate;
@property BOOL storesData;
#endif

/*!
 * @brief Creates a new OFHTTPRequest.
 *
 * @return A new, autoreleased OFHTTPRequest
 */







|

|
<
<
|
<








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





<

<
<
|
>





<

|
<
|







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 OFURL;
@class OFDictionary;
@class OFDataArray;


@class OFString;


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 class for storing HTTP requests.
 */
@interface OFHTTPRequest: OFObject
{
	OFURL *URL;
	of_http_request_type_t requestType;

	OFDictionary *headers;


	OFDataArray *postData;
	OFString *MIMEType;
}

#ifdef OF_HAVE_PROPERTIES
@property (copy) OFURL *URL;
@property of_http_request_type_t requestType;

@property (copy) OFDictionary *headers;
@property (retain) OFDataArray *postData;

@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
/*!
 * @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.
 *
 * @param delegate The delegate of the HTTP request
 */
- (void)setDelegate: (id <OFHTTPRequestDelegate>)delegate;

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

/*!
 * @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.
 *
 * @return Whether an OFDataArray with the data should be created

 */
- (BOOL)storesData;

/*!
 * @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.
 *
 * @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
 */
- (OFHTTPRequestResult*)performWithRedirects: (size_t)redirects;
@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;

/*!
 * @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;







<
<
<
<
<
<
<
<
<
<
<
<
<
<















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

|

|


|

|

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

|

<
>

<
|
<
<
|
<
<
<
<

|

<
<
<
|

|


















|
|
|


















<
<




<
<
<
<
<
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 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 the POST data of the HTTP request.
 *
 * @param postData The POST data of the HTTP request
 */
- (void)setPostData: (OFDataArray*)postData;

/*!
 * @brief Returns the POST data of the HTTP request.
 *
 * @return The POST data of the HTTP request
 */

- (OFDataArray*)postData;










/*!
 * @brief Sets the MIME type for the POST data.
 *

 * @param MIMEType The MIME type for the POST data
 */

- (void)setMIMEType: (OFString*)MIMEType;







/*!
 * @brief Returns the MIME type for the POST data.
 *



 * @return The MIME type for the POST data
 */
- (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

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


 * @return The data received for the HTTP request
 */
- (OFDataArray*)data;
@end





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







<
<
<
<
<



<



<
<
<
<
<
<
<
<
<
<


<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
















<
<
<
<
<







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"






#import "OFHTTPRequest.h"
#import "OFString.h"
#import "OFURL.h"

#import "OFDictionary.h"
#import "OFDataArray.h"











#import "macros.h"























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






	return self;
}

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

109
110
111
112
113
114
115

	return self;
}

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


	[super dealloc];
}

- (void)setURL: (OFURL*)URL_
{
	OF_SETTER(URL, URL_, YES, 1)







|
|
>







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

	return self;
}

- (void)dealloc
{
	[URL 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
}

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

	[sock writeString: @"Connection: close\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) {
		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];

			follow = [delegate request: self
			    shouldFollowRedirectTo: new];

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

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

					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 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_
{
	self = [super init];

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








<
<
<
<
<
<
<
<
<
<










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

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

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

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




|
|
|







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)setHeaders: (OFDictionary*)headers_
{
	OF_SETTER(headers, headers_, YES, 1)
}

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












































- (void)setPostData: (OFDataArray*)postData_








{




	OF_SETTER(postData, postData_, YES, 0)







}





































- (OFDataArray*)postData

{


	OF_GETTER(postData, YES)



}






















































































- (void)setMIMEType: (OFString*)MIMEType_


{





	OF_SETTER(MIMEType, MIMEType_, YES, 1)



}













































































- (OFString*)MIMEType


{



	OF_GETTER(MIMEType, YES)


















































































}
@end

@implementation OFHTTPRequestResult
- 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
	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








<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
<
151
152
153
154
155
156
157
158

























	OF_GETTER(headers, YES)
}

- (OFDataArray*)data
{
	OF_GETTER(data, 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
	if (!of_mutex_unlock(&mutex))
		@throw [OFUnlockFailedException exceptionWithClass: [self class]
							      lock: self];
}

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

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








|







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, 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
	if (!of_rmutex_unlock(&rmutex))
		@throw [OFUnlockFailedException exceptionWithClass: [self class]
							      lock: self];
}

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

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








|







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

#import "OFString.h"
#import "OFString_UTF8.h"
#import "OFArray.h"
#import "OFDictionary.h"
#import "OFFile.h"
#import "OFURL.h"

#import "OFHTTPRequest.h"
#import "OFDataArray.h"
#import "OFXMLElement.h"

#import "OFHTTPRequestFailedException.h"
#import "OFInitializationFailedException.h"
#import "OFInvalidArgumentException.h"







>







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
				  encoding: OF_STRING_ENCODING_AUTODETECT];
}

- initWithContentsOfURL: (OFURL*)URL
	       encoding: (of_string_encoding_t)encoding
{
	void *pool;

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


	request = [OFHTTPRequest requestWithURL: URL];
	result = [request perform];

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








>




















>

|







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 = [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
- (OFString*)name
{
	OF_GETTER(name, YES)
}

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

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

- (void)dealloc
{







|







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, 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
#import "OFStreamSocket.h"
#import "OFTCPSocket.h"
#import "OFTLSSocket.h"
#import "OFProcess.h"
#import "OFStreamObserver.h"

#import "OFHTTPRequest.h"


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

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







>







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
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}		\
       OFJSONTests.m			\
       OFListTests.m			\
       OFMD5HashTests.m			\
       OFNumberTests.m			\
       OFObjectTests.m			\
       ${OFPLUGINTESTS_M}		\
       OFSerializationTests.m		\











|







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		\
       ${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
- (void)dictionaryTests;
@end

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

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

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

@interface TestsAppDelegate (OFListTests)







|
|







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 (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
	[self streamTests];
	[self TCPSocketTests];
#ifdef OF_THREADS
	[self threadTests];
#endif
	[self URLTests];
#ifdef OF_THREADS
	[self HTTPRequestTests];
#endif
	[self XMLParserTests];
	[self XMLNodeTests];
	[self XMLElementBuilderTests];
	[self serializationTests];
	[self JSONTests];
#ifdef OF_PLUGINS







|







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 HTTPClientTests];
#endif
	[self XMLParserTests];
	[self XMLNodeTests];
	[self XMLElementBuilderTests];
	[self serializationTests];
	[self JSONTests];
#ifdef OF_PLUGINS