platform/common/http_server/test/http_body_test.dart

319 lines
9.7 KiB
Dart

// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'package:platform_http_server/http_server.dart';
import 'package:test/test.dart';
import 'http_fakes.dart';
void _testHttpClientResponseBody() {
void check(
String mimeType, List<int> content, dynamic expectedBody, String type,
[bool shouldFail = false]) async {
var server = await HttpServer.bind('localhost', 0);
server.listen((request) {
request.listen((_) {}, onDone: () {
request.response.headers.contentType = ContentType.parse(mimeType);
request.response.add(content);
request.response.close();
});
});
var client = HttpClient();
try {
var request = await client.get('localhost', server.port, '/');
var response = await request.close();
var body = await HttpBodyHandler.processResponse(response);
expect(shouldFail, isFalse);
expect(body.type, equals(type));
expect(body.response, isNotNull);
switch (type) {
case 'text':
case 'json':
expect(body.body, equals(expectedBody));
break;
default:
fail('bad body type');
}
} catch (_) {
if (!shouldFail) rethrow;
} finally {
client.close();
await server.close();
}
}
check('text/plain', 'body'.codeUnits, 'body', 'text');
check('text/plain; charset=utf-8', 'body'.codeUnits, 'body', 'text');
check('text/plain; charset=iso-8859-1', 'body'.codeUnits, 'body', 'text');
check('text/plain; charset=us-ascii', 'body'.codeUnits, 'body', 'text');
check('text/plain; charset=utf-8', [42], '*', 'text');
check('text/plain; charset=us-ascii', [142], null, 'text', true);
check('text/plain; charset=utf-8', [142], null, 'text', true);
check('application/json', '{"val": 5}'.codeUnits, {'val': 5}, 'json');
check('application/json', '{ bad json }'.codeUnits, null, 'json', true);
}
void _testHttpServerRequestBody() {
void check(
String? mimeType, List<int> content, dynamic expectedBody, String type,
{bool shouldFail = false, Encoding defaultEncoding = utf8}) async {
var server = await HttpServer.bind('localhost', 0);
server.transform(HttpBodyHandler(defaultEncoding: defaultEncoding)).listen(
(body) {
if (shouldFail) return;
expect(shouldFail, isFalse);
expect(body.type, equals(type));
switch (type) {
case 'text':
expect(
body.request.headers.contentType!.mimeType, equals('text/plain'));
expect(body.body, equals(expectedBody));
break;
case 'json':
expect(body.request.headers.contentType!.mimeType,
equals('application/json'));
expect(body.body, equals(expectedBody));
break;
case 'binary':
expect(body.request.headers.contentType, isNull);
expect(body.body, equals(expectedBody));
break;
case 'form':
var mimeType = body.request.headers.contentType!.mimeType;
expect(
mimeType,
anyOf(equals('multipart/form-data'),
equals('application/x-www-form-urlencoded')));
expect(body.body.keys.toSet(), equals(expectedBody.keys.toSet()));
for (var key in expectedBody.keys) {
var found = body.body[key];
var expected = expectedBody[key];
if (found is HttpBodyFileUpload) {
expect(found.contentType.toString(),
equals(expected['contentType']));
expect(found.filename, equals(expected['filename']));
expect(found.content, equals(expected['content']));
} else {
expect(found, equals(expected));
}
}
break;
default:
throw StateError('bad body type');
}
body.request.response.close();
}, onError: (Object error) {
// ignore: only_throw_errors
if (!shouldFail) throw error;
});
var client = HttpClient();
try {
var request = await client.post('localhost', server.port, '/');
if (mimeType != null) {
request.headers.contentType = ContentType.parse(mimeType);
}
request.add(content);
var response = await request.close();
if (shouldFail) {
expect(response.statusCode, equals(HttpStatus.badRequest));
}
return response.drain();
} catch (_) {
if (!shouldFail) rethrow;
} finally {
client.close();
await server.close();
}
}
check('text/plain', 'body'.codeUnits, 'body', 'text');
check('text/plain; charset=utf-8', 'body'.codeUnits, 'body', 'text');
check('text/plain; charset=utf-8', [42], '*', 'text');
check('text/plain; charset=us-ascii', [142], null, 'text', shouldFail: true);
check('text/plain; charset=utf-8', [142], null, 'text', shouldFail: true);
check('application/json', '{"val": 5}'.codeUnits, {'val': 5}, 'json');
check('application/json', '{ bad json }'.codeUnits, null, 'json',
shouldFail: true);
check(null, 'body'.codeUnits, 'body'.codeUnits, 'binary');
check(
'multipart/form-data; boundary=AaB03x',
'''
--AaB03x\r
Content-Disposition: form-data; name="name"\r
\r
Larry\r
--AaB03x--\r\n'''
.codeUnits,
{'name': 'Larry'},
'form');
check(
'multipart/form-data; boundary=AaB03x',
'''
--AaB03x\r
Content-Disposition: form-data; name="files"; filename="myfile"\r
Content-Type: application/octet-stream\r
\r
File content\r
--AaB03x--\r\n'''
.codeUnits,
{
'files': {
'filename': 'myfile',
'contentType': 'application/octet-stream',
'content': 'File content'.codeUnits
}
},
'form');
check(
'multipart/form-data; boundary=AaB03x',
'''
--AaB03x\r
Content-Disposition: form-data; name="files"; filename="myfile"\r
Content-Type: application/octet-stream\r
\r
File content\r
--AaB03x\r
Content-Disposition: form-data; name="files"; filename="myfile"\r
Content-Type: text/plain\r
\r
File content\r
--AaB03x--\r\n'''
.codeUnits,
{
'files': {
'filename': 'myfile',
'contentType': 'text/plain',
'content': 'File content'
}
},
'form');
check(
'multipart/form-data; boundary=AaB03x',
'''
--AaB03x\r
Content-Disposition: form-data; name="files"; filename="myfile"\r
Content-Type: application/json\r
\r
File content\r
--AaB03x--\r\n'''
.codeUnits,
{
'files': {
'filename': 'myfile',
'contentType': 'application/json',
'content': 'File content'
}
},
'form');
check(
'application/x-www-form-urlencoded',
'%E5%B9%B3%3D%E4%BB%AE%E5%90%8D=%E5%B9%B3%E4%BB%AE%E5%90%8D&b'
'=%E5%B9%B3%E4%BB%AE%E5%90%8D'
.codeUnits,
{'平=仮名': '平仮名', 'b': '平仮名'},
'form');
check('application/x-www-form-urlencoded', 'a=%F8+%26%23548%3B'.codeUnits,
null, 'form',
shouldFail: true);
check('application/x-www-form-urlencoded', 'a=%C0%A0'.codeUnits, null, 'form',
shouldFail: true);
check('application/x-www-form-urlencoded', 'a=x%A0x'.codeUnits, null, 'form',
shouldFail: true);
check('application/x-www-form-urlencoded', 'a=x%C0x'.codeUnits, null, 'form',
shouldFail: true);
check('application/x-www-form-urlencoded', 'a=%C3%B8+%C8%A4'.codeUnits,
{'a': 'ø Ȥ'}, 'form');
check('application/x-www-form-urlencoded', 'a=%F8+%26%23548%3B'.codeUnits,
{'a': 'ø &#548;'}, 'form',
defaultEncoding: latin1);
check('application/x-www-form-urlencoded', 'name=%26'.codeUnits,
{'name': '&'}, 'form',
defaultEncoding: latin1);
check('application/x-www-form-urlencoded', 'name=%F8%26'.codeUnits,
{'name': 'ø&'}, 'form',
defaultEncoding: latin1);
check('application/x-www-form-urlencoded', 'name=%26%3B'.codeUnits,
{'name': '&;'}, 'form',
defaultEncoding: latin1);
check(
'application/x-www-form-urlencoded',
'name=%26%23548%3B%26%23548%3B'.codeUnits,
{'name': '&#548;&#548;'},
'form',
defaultEncoding: latin1);
check('application/x-www-form-urlencoded', 'name=%26'.codeUnits,
{'name': '&'}, 'form');
check('application/x-www-form-urlencoded', 'name=%C3%B8%26'.codeUnits,
{'name': 'ø&'}, 'form');
check('application/x-www-form-urlencoded', 'name=%26%3B'.codeUnits,
{'name': '&;'}, 'form');
check('application/x-www-form-urlencoded',
'name=%C8%A4%26%23548%3B'.codeUnits, {'name': 'Ȥ&#548;'}, 'form');
check('application/x-www-form-urlencoded', 'name=%C8%A4%C8%A4'.codeUnits,
{'name': 'ȤȤ'}, 'form');
}
void main() {
test('client response body', _testHttpClientResponseBody);
test('server request body', _testHttpServerRequestBody);
test('Does not close stream while requests are pending', () async {
var data = StreamController<Uint8List>();
var requests = Stream<HttpRequest>.fromIterable(
[FakeHttpRequest(Uri(), data: data.stream)]);
var isDone = false;
requests
.transform(HttpBodyHandler())
.listen((_) {}, onDone: () => isDone = true);
await pumpEventQueue();
expect(isDone, isFalse);
await data.close();
expect(isDone, isTrue);
});
test('Closes stream while no requests are pending', () async {
var requests = Stream<HttpRequest>.empty();
var isDone = false;
requests
.transform(HttpBodyHandler())
.listen((_) {}, onDone: () => isDone = true);
await pumpEventQueue();
expect(isDone, isTrue);
});
}