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
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];
	OFMutableDictionary OF_GENERIC(OFString*, OFString*) *headers;
	OFDataArray *body = [request body];
	OFTCPSocket *socket;
	OFHTTPClientResponse *response;
	OFString *line, *version, *redirect, *connectionHeader;
	bool keepAlive;
	OFMutableDictionary *serverHeaders;
	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
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))
		[requestString appendFormat: @"Host: %@:%d\r\n",
					     [URL host], [URL port]];
	else
		[requestString appendFormat: @"Host: %@\r\n", [URL host]];
	    ([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 *authorization = [OFDataArray dataArray];
		OFDataArray *authorizationData = [OFDataArray dataArray];
		OFString *authorization;

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

		authorization = [OFString stringWithFormat:
		    @"Basic %@", [authorizationData stringByBase64Encoding]];
		[requestString appendFormat:
		    @"Authorization: Basic %@\r\n",
		    [authorization stringByBase64Encoding]];

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

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

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

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

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

	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;

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
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();
	OFString *date = [[OFDate date]
	OFMutableDictionary OF_GENERIC(OFString*, OFString*) *headers;
	    dateStringWithFormat: @"%a, %d %b %Y %H:%M:%S GMT"];
	OFEnumerator *keyEnumerator, *valueEnumerator;
	OFString *key, *value;

	[_socket writeFormat: @"HTTP/%@ %d %s\r\n"
	[_socket writeFormat: @"HTTP/%@ %d %s\r\n",
			      @"Server: %@\r\n"
			      @"Date: %@\r\n",
			      [self protocolVersionString], _statusCode,
			      statusCodeToString(_statusCode),
			      [_server name], date];
			      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];
	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 writeFormat: @"%@: %@\r\n", key, value];

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

	_headersSent = true;
	_chunked = [[_headers objectForKey: @"Transfer-Encoding"]
	_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
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://webkeks.org/objfw/>)";
	    @"<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
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:
	if (![[client readLine] hasPrefix: @"User-Agent:"])
	    [OFString stringWithFormat: @"Host: 127.0.0.1:%" @PRIu16, _port]])
		OF_ENSURE(0);

	if (![[client readLine] hasPrefix: @"User-Agent:"])
	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
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;
	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:
	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(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]) &&