ObjFW  Check-in [58d4025602]

Overview
Comment:Better randomization of HTTP header order

This should randomize the order of all headers now, preventing
fingerprinting by the order of HTTP headers.

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 58d4025602a056482d499aae532513ee4236dc824696531c5ac745d3068db432
User & Date: js on 2016-01-05 14:10:52
Other Links: manifest | tags
Context
2016-01-05
14:55
OFHTTPServer: Allow setting name to nil check-in: 6b4d138cc3 user: js tags: trunk
14:10
Better randomization of HTTP header order check-in: 58d4025602 user: js tags: trunk
2016-01-03
01:14
Make more use of fast enumeration check-in: cb0fd980f9 user: js tags: trunk
Changes

Modified src/OFHTTPClient.m from [f0a87a8790] to [ad2a18a279].

309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
{
	void *pool = objc_autoreleasePoolPush();
	OFURL *URL = [request URL];
	OFString *scheme = [URL scheme];
	of_http_request_method_t method = [request method];
	OFMutableString *requestString;
	OFString *user, *password;
	OFDictionary *headers = [request headers];
	OFDataArray *body = [request body];
	OFTCPSocket *socket;
	OFHTTPClientResponse *response;
	OFString *line, *version, *redirect, *connectionHeader;
	bool keepAlive;
	OFMutableDictionary *serverHeaders;
	OFEnumerator *keyEnumerator, *objectEnumerator;
	OFString *key, *object;
	int status;

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








|





|







309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
{
	void *pool = objc_autoreleasePoolPush();
	OFURL *URL = [request URL];
	OFString *scheme = [URL scheme];
	of_http_request_method_t method = [request method];
	OFMutableString *requestString;
	OFString *user, *password;
	OFMutableDictionary OF_GENERIC(OFString*, OFString*) *headers;
	OFDataArray *body = [request body];
	OFTCPSocket *socket;
	OFHTTPClientResponse *response;
	OFString *line, *version, *redirect, *connectionHeader;
	bool keepAlive;
	OFMutableDictionary OF_GENERIC(OFString*, OFString*) *serverHeaders;
	OFEnumerator *keyEnumerator, *objectEnumerator;
	OFString *key, *object;
	int status;

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

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
		    of_http_request_method_to_string(method), [URL path],
		    [URL query], [request protocolVersionString]];
	else
		requestString = [OFMutableString stringWithFormat:
		    @"%s /%@ HTTP/%@\r\n",
		    of_http_request_method_to_string(method), [URL path],
		    [request protocolVersionString]];





	if (([scheme isEqual: @"http"] && [URL port] != 80) ||
	    ([scheme isEqual: @"https"] && [URL port] != 443))
		[requestString appendFormat: @"Host: %@:%d\r\n",
					     [URL host], [URL port]];



	else

		[requestString appendFormat: @"Host: %@\r\n", [URL host]];

	user = [URL user];
	password = [URL password];

	if ([user length] > 0 || [password length] > 0) {
		OFDataArray *authorization = [OFDataArray dataArray];


		[authorization addItems: [user UTF8String]
				  count: [user UTF8StringLength]];
		[authorization addItem: ":"];
		[authorization addItems: [password UTF8String]
				  count: [password UTF8StringLength]];



		[requestString appendFormat:
		    @"Authorization: Basic %@\r\n",
		    [authorization stringByBase64Encoding]];
	}

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


	if (body != nil) {
		if ([headers objectForKey: @"Content-Length"] == nil)
			[requestString appendFormat:
			    @"Content-Length: %zd\r\n",
			    [body itemSize] * [body count]];





		if ([headers objectForKey: @"Content-Type"] == nil)
			[requestString appendString:
			    @"Content-Type: application/x-www-form-urlencoded; "
			    @"charset=UTF-8\r\n"];

	}






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

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

	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;








>
>
>
>

|
|
|
>
>
>
|
>
|





|
>

|
|
|
|
|

>
>
|
|
|



<
|
|
>


|
|
<
|

>
>
>
>

<
|
|
>

>
>
>
>
>








<
<
<
<







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
		    of_http_request_method_to_string(method), [URL path],
		    [URL query], [request protocolVersionString]];
	else
		requestString = [OFMutableString stringWithFormat:
		    @"%s /%@ HTTP/%@\r\n",
		    of_http_request_method_to_string(method), [URL path],
		    [request protocolVersionString]];

	headers = [[[request headers] mutableCopy] autorelease];
	if (headers == nil)
		headers = [OFMutableDictionary dictionary];

	if (([scheme isEqual: @"http"] && [URL port] != 80) ||
	    ([scheme isEqual: @"https"] && [URL port] != 443)) {
		OFString *host = [OFString stringWithFormat:
		    @"%@:%d", [URL host], [URL port]];

		[headers setObject: host
			    forKey: @"Host"];
	} else
		[headers setObject: [URL host]
			    forKey: @"Host"];

	user = [URL user];
	password = [URL password];

	if ([user length] > 0 || [password length] > 0) {
		OFDataArray *authorizationData = [OFDataArray dataArray];
		OFString *authorization;

		[authorizationData addItems: [user UTF8String]
				      count: [user UTF8StringLength]];
		[authorizationData addItem: ":"];
		[authorizationData addItems: [password UTF8String]
				      count: [password UTF8StringLength]];

		authorization = [OFString stringWithFormat:
		    @"Basic %@", [authorizationData stringByBase64Encoding]];

		[headers setObject: authorization
			    forKey: @"Authorization"];
	}

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

		[headers setObject: @"Something using ObjFW "
				    @"<https://heap.zone/objfw>"
			    forKey: @"User-Agent"];

	if (body != nil) {
		if ([headers objectForKey: @"Content-Length"] == nil) {
			OFString *contentLength = [OFString stringWithFormat:

			    @"%zd", [body itemSize] * [body count]];

			[headers setObject: contentLength
				    forKey: @"Content-Length"];
		}

		if ([headers objectForKey: @"Content-Type"] == nil)

			[headers setObject: @"application/x-www-form-"
					    @"urlencoded; charset=UTF-8"
				    forKey: @"Content-Type"];
	}

	if ([request protocolVersion].major == 1 &&
	    [request protocolVersion].minor == 0)
		[headers setObject: @"keep-alive"
			    forKey: @"Connection"];

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

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





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

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

Modified src/OFHTTPServer.m from [9cfe1fbff0] to [5b5e9e2174].

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

	[super dealloc];
}

- (void)OF_sendHeaders
{
	void *pool = objc_autoreleasePoolPush();
	OFString *date = [[OFDate date]
	    dateStringWithFormat: @"%a, %d %b %Y %H:%M:%S GMT"];
	OFEnumerator *keyEnumerator, *valueEnumerator;
	OFString *key, *value;

	[_socket writeFormat: @"HTTP/%@ %d %s\r\n"
			      @"Server: %@\r\n"
			      @"Date: %@\r\n",
			      [self protocolVersionString], _statusCode,
			      statusCodeToString(_statusCode),












			      [_server name], date];


	keyEnumerator = [_headers keyEnumerator];
	valueEnumerator = [_headers objectEnumerator];
	while ((key = [keyEnumerator nextObject]) != nil &&
	    (value = [valueEnumerator nextObject]) != nil)
		if (![key isEqual: @"Server"] && ![key isEqual: @"Date"])
			[_socket writeFormat: @"%@: %@\r\n", key, value];

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

	_headersSent = true;
	_chunked = [[_headers objectForKey: @"Transfer-Encoding"]
	    isEqual: @"chunked"];

	objc_autoreleasePoolPop(pool);
}

- (void)lowlevelWriteBuffer: (const void*)buffer
		     length: (size_t)length







|
<



|
<
<

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

|
|


<
|




|







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

	[super dealloc];
}

- (void)OF_sendHeaders
{
	void *pool = objc_autoreleasePoolPush();
	OFMutableDictionary OF_GENERIC(OFString*, OFString*) *headers;

	OFEnumerator *keyEnumerator, *valueEnumerator;
	OFString *key, *value;

	[_socket writeFormat: @"HTTP/%@ %d %s\r\n",


			      [self protocolVersionString], _statusCode,
			      statusCodeToString(_statusCode)];

	headers = [[_headers mutableCopy] autorelease];

	if ([headers objectForKey: @"Date"] == nil) {
		OFString *date = [[OFDate date]
		    dateStringWithFormat: @"%a, %d %b %Y %H:%M:%S GMT"];

		[headers setObject: date
			    forKey: @"Date"];
	}

	if ([headers objectForKey: @"Server"] == nil)
		[headers setObject: [_server name]
			    forKey: @"Server"];

	keyEnumerator = [headers keyEnumerator];
	valueEnumerator = [headers objectEnumerator];
	while ((key = [keyEnumerator nextObject]) != nil &&
	    (value = [valueEnumerator nextObject]) != nil)

		[_socket writeFormat: @"%@: %@\r\n", key, value];

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

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

	objc_autoreleasePoolPop(pool);
}

- (void)lowlevelWriteBuffer: (const void*)buffer
		     length: (size_t)length
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
}

- init
{
	self = [super init];

	_name = @"OFHTTPServer (ObjFW's HTTP server class "
	    @"<https://webkeks.org/objfw/>)";

	return self;
}

- (void)dealloc
{
	[_host release];







|







663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
}

- init
{
	self = [super init];

	_name = @"OFHTTPServer (ObjFW's HTTP server class "
	    @"<https://heap.zone/objfw/>)";

	return self;
}

- (void)dealloc
{
	[_host release];

Modified tests/OFHTTPClientTests.m from [d1280b74ae] to [b7f6ba3e3b].

58
59
60
61
62
63
64
65
66
67
68
69

70
71
72
73
74
75
76
	[cond unlock];

	client = [listener accept];

	if (![[client readLine] isEqual: @"GET /foo HTTP/1.1"])
		OF_ENSURE(0);

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

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

		OF_ENSURE(0);

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

	[client writeString: @"HTTP/1.0 200 OK\r\n"
			     @"cONTeNT-lENgTH: 7\r\n"







|
<


|
>







58
59
60
61
62
63
64
65

66
67
68
69
70
71
72
73
74
75
76
	[cond unlock];

	client = [listener accept];

	if (![[client readLine] isEqual: @"GET /foo HTTP/1.1"])
		OF_ENSURE(0);

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

		OF_ENSURE(0);

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

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

	[client writeString: @"HTTP/1.0 200 OK\r\n"
			     @"cONTeNT-lENgTH: 7\r\n"
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
@end

@implementation TestsAppDelegate (OFHTTPClientTests)
- (void)HTTPClientTests
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
	HTTPClientTestsServer *server;
	OFURL *url;
	OFHTTPClient *client;
	OFHTTPRequest *request;
	OFHTTPResponse *response = nil;
	OFDataArray *data;

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

	server = [[[HTTPClientTestsServer 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:]",
	    (client = [OFHTTPClient client]) &&
	    R(request = [OFHTTPRequest requestWithURL: url]) &&
	    R(response = [client performRequest: request]))

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

	TEST(@"Correct parsing of data",
	    (data = [response readDataArrayTillEndOfStream]) &&







|














|





|







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

@implementation TestsAppDelegate (OFHTTPClientTests)
- (void)HTTPClientTests
{
	OFAutoreleasePool *pool = [[OFAutoreleasePool alloc] init];
	HTTPClientTestsServer *server;
	OFURL *URL;
	OFHTTPClient *client;
	OFHTTPRequest *request;
	OFHTTPResponse *response = nil;
	OFDataArray *data;

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

	server = [[[HTTPClientTestsServer 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:]",
	    (client = [OFHTTPClient client]) &&
	    R(request = [OFHTTPRequest requestWithURL: URL]) &&
	    R(response = [client performRequest: request]))

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

	TEST(@"Correct parsing of data",
	    (data = [response readDataArrayTillEndOfStream]) &&