ObjFW  Check-in [7be34d8c40]

Overview
Comment:OFHTTP{Client,Server}: Handle all request methods.
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 7be34d8c40e6c51066d05c641c41168ffc260d8425c60dace0adc4e9c023724c
User & Date: js on 2013-09-28 15:25:18
Other Links: manifest | tags
Context
2013-09-29
16:25
Add missing includes. check-in: c6a65ca7a6 user: js tags: trunk
2013-09-28
15:25
OFHTTP{Client,Server}: Handle all request methods. check-in: 7be34d8c40 user: js tags: trunk
01:50
Improve HTTP request method handling. check-in: c76896d937 user: js tags: trunk
Changes

Modified src/OFHTTPClient.m from [97ab76e832] to [95811098ef].

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







-
+











-
-
-
-
-
-







{
	void *pool = objc_autoreleasePoolPush();
	OFURL *URL = [request URL];
	OFString *scheme = [URL scheme];
	of_http_request_method_t method = [request method];
	OFMutableString *requestString;
	OFDictionary *headers = [request headers];
	OFDataArray *POSTData = [request POSTData];
	OFDataArray *entity = [request entity];
	OFTCPSocket *socket;
	OFHTTPClientResponse *response;
	OFString *line, *path, *version, *redirect, *keepAlive;
	OFMutableDictionary *serverHeaders;
	OFEnumerator *keyEnumerator, *objectEnumerator;
	OFString *key, *object;
	int status;

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

	if (method != OF_HTTP_REQUEST_METHOD_GET &&
	    method != OF_HTTP_REQUEST_METHOD_HEAD &&
	    method != OF_HTTP_REQUEST_METHOD_POST)
		@throw [OFNotImplementedException exceptionWithSelector: _cmd
								 object: self];

	/* Can we reuse the socket? */
	if (_socket != nil && [[_lastURL scheme] isEqual: [URL scheme]] &&
	    [[_lastURL host] isEqual: [URL host]] &&
	    [_lastURL port] == [URL port]) {
		/*
		 * Set _socket to nil, so that in case of an error it won't be
		 * reused. If everything is successfull, we set _socket again
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
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







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

















-
-
-
+
+
+
















-
-
-
-
+
+
+
+







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

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

	if (method == OF_HTTP_REQUEST_METHOD_POST) {
		OFString *contentType = [request MIMEType];

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

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

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

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

	@try {
		[socket writeString: requestString];
	} @catch (OFWriteFailedException *e) {
		if ([e errNo] != ECONNRESET && [e errNo] != EPIPE)
			@throw e;

		/* Reconnect in case a keep-alive connection timed out */
		socket = [self OF_createSocketForRequest: request];
		[socket writeString: requestString];
	}

	if (method == OF_HTTP_REQUEST_METHOD_POST)
		[socket writeBuffer: [POSTData items]
			     length: [POSTData count] * [POSTData itemSize]];
	if (entity != nil)
		[socket writeBuffer: [entity items]
			     length: [entity count] * [entity itemSize]];

	@try {
		line = [socket readLine];
	} @catch (OFInvalidEncodingException *e) {
		@throw [OFInvalidServerReplyException exception];
	}

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

		if (method == OF_HTTP_REQUEST_METHOD_POST)
			[socket writeBuffer: [POSTData items]
				     length: [POSTData count] *
					     [POSTData itemSize]];
		if (entity != nil)
			[socket writeBuffer: [entity items]
				     length: [entity count] *
					     [entity itemSize]];

		@try {
			line = [socket readLine];
		} @catch (OFInvalidEncodingException *e) {
			@throw [OFInvalidServerReplyException exception];
		}
	}
576
577
578
579
580
581
582
583

584
585
586















587
588
589
590


591
592
593
594
595
596
597
557
558
559
560
561
562
563

564

565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583


584
585
586
587
588
589
590
591
592







-
+
-


+
+
+
+
+
+
+
+
+
+
+
+
+
+
+


-
-
+
+








		if (follow) {
			OFHTTPRequest *newRequest;

			newRequest = [OFHTTPRequest requestWithURL: newURL];
			[newRequest setMethod: method];
			[newRequest setHeaders: headers];
			[newRequest setPOSTData: POSTData];
			[newRequest setEntity: entity];
			[newRequest setMIMEType: [request MIMEType]];

			if (status == 303) {
				OFMutableDictionary *newHeaders;
				OFEnumerator *keyEnumerator, *objectEnumerator;
				id key, object;

				newHeaders = [OFMutableDictionary dictionary];
				keyEnumerator = [headers keyEnumerator];
				objectEnumerator = [headers objectEnumerator];
				while ((key = [keyEnumerator nextObject]) !=
				    nil &&
				    (object = [objectEnumerator nextObject]) !=
				    nil)
					if (![key hasPrefix: @"Content-"])
						[newHeaders setObject: object
							       forKey: key];

				[newRequest
				    setMethod: OF_HTTP_REQUEST_METHOD_GET];
				[newRequest setPOSTData: nil];
				[newRequest setMIMEType: nil];
				[newRequest setHeaders: newHeaders];
				[newRequest setEntity: nil];
			}

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

			return [self performRequest: newRequest

Modified src/OFHTTPRequest.h from [3c79d758cd] to [f347146186].

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
64
65
66
67
68
69
70

71

72
73
74
75
76
77
78
79

80

81
82
83
84
85
86
87







-
+
-








-
+
-







 */
@interface OFHTTPRequest: OFObject <OFCopying>
{
	OFURL *_URL;
	of_http_request_method_t _method;
	of_http_request_protocol_version_t _protocolVersion;
	OFDictionary *_headers;
	OFDataArray *_POSTData;
	OFDataArray *_entity;
	OFString *_MIMEType;
	OFString *_remoteAddress;
}

#ifdef OF_HAVE_PROPERTIES
@property (copy) OFURL *URL;
@property of_http_request_method_t method;
@property of_http_request_protocol_version_t protocolVersion;
@property (copy) OFDictionary *headers;
@property (retain) OFDataArray *POSTData;
@property (retain) OFDataArray *entity;
@property (copy) OFString *MIMEType;
@property (copy) OFString *remoteAddress;
#endif

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







-
+

-
+

-
+


-
+

-
+

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







 * @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.
 * @brief Sets the entity body of the HTTP request.
 *
 * @param POSTData The POST data of the HTTP request
 * @param entity The entity body of the HTTP request
 */
- (void)setPOSTData: (OFDataArray*)POSTData;
- (void)setEntity: (OFDataArray*)entity;

/*!
 * @brief Returns the POST data of the HTTP request.
 * @brief Returns the entity body of the HTTP request.
 *
 * @return The POST data of the HTTP request
 * @return The entity body of the HTTP request
 */
- (OFDataArray*)POSTData;
- (OFDataArray*)entity;

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

/*!
 * @brief Sets the remote address from which the request originates.
 *
 * @param remoteAddress The remote address from which the request originates
 */
- (void)setRemoteAddress: (OFString*)remoteAddress;

Modified src/OFHTTPRequest.m from [e321453562] to [703332cbc9].

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







-
+
-














-
+
-







	return self;
}

- (void)dealloc
{
	[_URL release];
	[_headers release];
	[_POSTData release];
	[_entity release];
	[_MIMEType release];
	[_remoteAddress release];

	[super dealloc];
}

- copy
{
	OFHTTPRequest *copy = [[OFHTTPRequest alloc] init];

	@try {
		copy->_method = _method;
		copy->_protocolVersion = _protocolVersion;
		[copy setURL: _URL];
		[copy setHeaders: _headers];
		[copy setPOSTData: _POSTData];
		[copy setEntity: _entity];
		[copy setMIMEType: _MIMEType];
		[copy setRemoteAddress: _remoteAddress];
	} @catch (id e) {
		[copy release];
		@throw e;
	}

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







-
+
-

















-
+
-







	request = object;

	if (request->_method != _method ||
	    request->_protocolVersion.major != _protocolVersion.major ||
	    request->_protocolVersion.minor != _protocolVersion.minor ||
	    ![request->_URL isEqual: _URL] ||
	    ![request->_headers isEqual: _headers] ||
	    ![request->_POSTData isEqual: _POSTData] ||
	    ![request->_entity isEqual: _entity] ||
	    ![request->_MIMEType isEqual: _MIMEType] ||
	    ![request->_remoteAddress isEqual: _remoteAddress])
		return false;

	return true;
}

- (uint32_t)hash
{
	uint32_t hash;

	OF_HASH_INIT(hash);

	OF_HASH_ADD(hash, _method);
	OF_HASH_ADD(hash, _protocolVersion.major);
	OF_HASH_ADD(hash, _protocolVersion.minor);
	OF_HASH_ADD_HASH(hash, [_URL hash]);
	OF_HASH_ADD_HASH(hash, [_headers hash]);
	OF_HASH_ADD_HASH(hash, [_POSTData hash]);
	OF_HASH_ADD_HASH(hash, [_entity hash]);
	OF_HASH_ADD_HASH(hash, [_MIMEType hash]);
	OF_HASH_ADD_HASH(hash, [_remoteAddress hash]);

	OF_HASH_FINALIZE(hash);

	return hash;
}

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







-
+

-
+


-
+

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
















-
+




-
+







-
+
-


-
-
+
+






}

- (OFDictionary*)headers
{
	OF_GETTER(_headers, true)
}

- (void)setPOSTData: (OFDataArray*)POSTData
- (void)setEntity: (OFDataArray*)entity
{
	OF_SETTER(_POSTData, POSTData, true, 0)
	OF_SETTER(_entity, entity, true, 0)
}

- (OFDataArray*)POSTData
- (OFDataArray*)entity
{
	OF_GETTER(_POSTData, true)
	OF_GETTER(_entity, true)
}

- (void)setMIMEType: (OFString*)MIMEType
{
	OF_SETTER(_MIMEType, MIMEType, true, 1)
}

- (OFString*)MIMEType
{
	OF_GETTER(_MIMEType, true)
}

- (void)setRemoteAddress: (OFString*)remoteAddress
{
	OF_SETTER(_remoteAddress, remoteAddress, true, 1)
}

- (OFString*)remoteAddress
{
	OF_GETTER(_remoteAddress, true)
}

- (OFString*)description
{
	void *pool = objc_autoreleasePoolPush();
	const char *method = of_http_request_method_to_string(_method);
	OFString *indentedHeaders, *indentedPOSTData, *ret;
	OFString *indentedHeaders, *indentedEntity, *ret;

	indentedHeaders = [[_headers description]
	    stringByReplacingOccurrencesOfString: @"\n"
				      withString: @"\n\t"];
	indentedPOSTData = [[_POSTData description]
	indentedEntity = [[_entity description]
	    stringByReplacingOccurrencesOfString: @"\n"
				      withString: @"\n\t"];

	ret = [[OFString alloc] initWithFormat:
	    @"<%@:\n\tURL = %@\n"
	    @"\tMethod = %s\n"
	    @"\tHeaders = %@\n"
	    @"\tPOST data = %@\n"
	    @"\tEntity = %@\n"
	    @"\tPOST data MIME type = %@\n"
	    @"\tRemote address = %@\n"
	    @">",
	    [self class], _URL, method, indentedHeaders, indentedPOSTData,
	    _MIMEType, _remoteAddress];
	    [self class], _URL, method, indentedHeaders, indentedEntity,
	    _remoteAddress];

	objc_autoreleasePoolPop(pool);

	return [ret autorelease];
}
@end

Modified src/OFHTTPServer.m from [86738768aa] to [9bf4d2786d].

292
293
294
295
296
297
298
299

300
301
302
303
304
305
306
292
293
294
295
296
297
298

299
300
301
302
303
304
305
306







-
+







	} _state;
	uint8_t _HTTPMinorVersion;
	of_http_request_method_t _method;
	OFString *_host, *_path;
	uint16_t _port;
	OFMutableDictionary *_headers;
	size_t _contentLength;
	OFDataArray *_POSTData;
	OFDataArray *_entity;
}

- initWithSocket: (OFTCPSocket*)socket
	  server: (OFHTTPServer*)server;
- (bool)socket: (OFTCPSocket*)socket
   didReadLine: (OFString*)line
     exception: (OFException*)exception;
345
346
347
348
349
350
351
352

353
354
355
356
357
358
359
345
346
347
348
349
350
351

352
353
354
355
356
357
358
359







-
+








	[_timer invalidate];
	[_timer release];

	[_host release];
	[_path release];
	[_headers release];
	[_POSTData release];
	[_entity release];

	[super dealloc];
}

- (bool)socket: (OFTCPSocket*)socket
   didReadLine: (OFString*)line
     exception: (OFException*)exception
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
414
415
416
417
418
419
420




421
422
423
424
425
426
427







-
-
-
-







	method = [line substringWithRange: of_range(0, pos)];
	@try {
		_method = of_http_request_method_from_string(
		    [method UTF8String]);
	} @catch (OFInvalidFormatException *e) {
		return [self sendErrorAndClose: 405];
	}
	if (_method != OF_HTTP_REQUEST_METHOD_GET &&
	    _method != OF_HTTP_REQUEST_METHOD_HEAD &&
	    _method != OF_HTTP_REQUEST_METHOD_POST)
		return [self sendErrorAndClose: 405];

	@try {
		_path = [line substringWithRange:
		    of_range(pos + 1, [line length] - pos - 10)];
	} @catch (OFOutOfRangeException *e) {
		return [self sendErrorAndClose: 400];
	}
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
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







-
-
-
-
-
+
-
-
-

-
-
-
-
-
+
+
+
+
+
+

+
+
+

-
+











-
-
-


+








- (bool)parseHeaders: (OFString*)line
{
	OFString *key, *value;
	size_t pos;

	if ([line length] == 0) {
		switch (_method) {
		case OF_HTTP_REQUEST_METHOD_POST:;
			OFString *tmp;
			char *buffer;

		size_t contentLength;
			tmp = [_headers objectForKey: @"Content-Length"];
			if (tmp == nil)
				return [self sendErrorAndClose: 411];

			@try {
				_contentLength = (size_t)[tmp decimalValue];
			} @catch (OFInvalidFormatException *e) {
				return [self sendErrorAndClose: 400];
			}
		@try {
			contentLength = [[_headers
			    objectForKey: @"Content-Length"] decimalValue];
		} @catch (OFInvalidFormatException *e) {
			return [self sendErrorAndClose: 400];
		}

		if (contentLength > 0) {
			char *buffer;

			buffer = [self allocMemoryWithSize: BUFFER_SIZE];
			_POSTData = [[OFDataArray alloc] init];
			_entity = [[OFDataArray alloc] init];

			[_socket asyncReadIntoBuffer: buffer
					      length: BUFFER_SIZE
					      target: self
					    selector: @selector(socket:
							  didReadIntoBuffer:
							  length:exception:)];
			[_timer setFireDate:
			    [OFDate dateWithTimeIntervalSinceNow: 5]];

			return false;
		default:
			_state = SEND_RESPONSE;
			break;
		}

		_state = SEND_RESPONSE;
		return true;
	}

	pos = [line rangeOfString: @":"].location;
	if (pos == OF_NOT_FOUND)
		return [self sendErrorAndClose: 400];

533
534
535
536
537
538
539
540
541


542
543

544
545
546
547
548
549
550
524
525
526
527
528
529
530


531
532
533

534
535
536
537
538
539
540
541







-
-
+
+

-
+







  didReadIntoBuffer: (const char*)buffer
	     length: (size_t)length
	  exception: (OFException*)exception
{
	if ([socket isAtEndOfStream] || exception != nil)
		return false;

	[_POSTData addItems: buffer
		      count: length];
	[_entity addItems: buffer
		    count: length];

	if ([_POSTData count] >= _contentLength) {
	if ([_entity count] >= _contentLength) {
		@try {
			[self createResponse];
		} @catch (OFWriteFailedException *e) {
			return false;
		}

		return false;
611
612
613
614
615
616
617
618

619
620
621
622
623
624
625
602
603
604
605
606
607
608

609
610
611
612
613
614
615
616







-
+







		[URL setPath: _path];

	request = [OFHTTPRequest requestWithURL: URL];
	[request setMethod: _method];
	[request setProtocolVersion:
	    (of_http_request_protocol_version_t){ 1, _HTTPMinorVersion }];
	[request setHeaders: _headers];
	[request setPOSTData: _POSTData];
	[request setEntity: _entity];
	[request setRemoteAddress: [_socket remoteAddress]];

	response = [[[OFHTTPServerResponse alloc]
	    initWithSocket: _socket
		    server: _server] autorelease];

	[[_server delegate] server: _server