// 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:mirrors'; import 'dart:typed_data'; import 'package:platform_http_server/http_server.dart'; import 'package:test/test.dart'; import 'package:test_api/src/backend/invoker.dart'; import 'http_fakes.dart'; Object get currentTestCase => Invoker.current!.liveTest; late SecurityContext serverContext; SecurityContext? clientContext; /// Used to flag a given test case as being a fake or not. final _isFakeTestExpando = Expando('isFakeTest'); void testVirtualDir(String name, Future Function(Directory) func) { _testVirtualDir(name, false, func); _testVirtualDir(name, true, func); } void _testVirtualDir( String name, bool useFakes, Future Function(Directory) func) { if (useFakes) { name = '$name, with fakes'; } test(name, () async { // see subsequent access to this expando below _isFakeTestExpando[currentTestCase] = useFakes; var dir = Directory.systemTemp.createTempSync('http_server_virtual_'); try { await func(dir); } finally { await dir.delete(recursive: true); } }); } Future statusCodeForVirtDir(VirtualDirectory virtualDir, String path, {String? host, bool secure = false, DateTime? ifModifiedSince, bool rawPath = false, bool followRedirects = true, int? from, int? to}) async { // if this is a fake test, then run the fake code path if (_isFakeTestExpando[currentTestCase]!) { var uri = _localhostUri(0, path, secure: secure, rawPath: rawPath); var request = FakeHttpRequest(uri, followRedirects: followRedirects, ifModifiedSince: ifModifiedSince, data: StreamController().stream); _addRangeHeader(request, from, to); var response = await _withFakeRequest(virtualDir, request); return response.statusCode; } assert(_isFakeTestExpando[currentTestCase] == false); return _withServer(virtualDir, (port) { return fetchStatusCode(port, path, host: host, secure: secure, ifModifiedSince: ifModifiedSince, rawPath: rawPath, followRedirects: followRedirects, from: from, to: to); }); } Future fetchStatusCode(int port, String path, {String? host, bool secure = false, DateTime? ifModifiedSince, bool rawPath = false, bool followRedirects = true, int? from, int? to}) async { var uri = _localhostUri(port, path, secure: secure, rawPath: rawPath); HttpClient client; if (secure) { client = HttpClient(context: clientContext); } else { client = HttpClient(); } try { var request = await client.getUrl(uri); if (!followRedirects) request.followRedirects = false; if (host != null) request.headers.host = host; if (ifModifiedSince != null) { request.headers.ifModifiedSince = ifModifiedSince; } _addRangeHeader(request, from, to); var response = await request.close(); await response.drain(); return response.statusCode; } finally { client.close(); } } Future fetchHEaders(VirtualDirectory virDir, String path, {int? from, int? to}) async { // if this is a fake test, then run the fake code path if (_isFakeTestExpando[currentTestCase]!) { var uri = _localhostUri(0, path); var request = FakeHttpRequest(uri, data: StreamController().stream); _addRangeHeader(request, from, to); var response = await _withFakeRequest(virDir, request); return response.headers; } assert(_isFakeTestExpando[currentTestCase] == false); return _withServer(virDir, (port) => _headers(port, path, from, to)); } Future fetchAsString(VirtualDirectory virtualDir, String path) async { // if this is a fake test, then run the fake code path if (_isFakeTestExpando[currentTestCase]!) { var uri = _localhostUri(0, path); var request = FakeHttpRequest(uri, data: StreamController().stream); var response = await _withFakeRequest(virtualDir, request); return response.fakeContent; } assert(_isFakeTestExpando[currentTestCase] == false); return _withServer(virtualDir, (int port) => _fetchAsString(port, path)); } Future> fetchAsBytes(VirtualDirectory virtualDir, String path, {int? from, int? to}) async { // if this is a fake test, then run the fake code path if (_isFakeTestExpando[currentTestCase]!) { var uri = _localhostUri(0, path); var request = FakeHttpRequest(uri, data: StreamController().stream); _addRangeHeader(request, from, to); var response = await _withFakeRequest(virtualDir, request); return response.fakeContentBinary; } assert(_isFakeTestExpando[currentTestCase] == false); return _withServer( virtualDir, (int port) => _fetchAsBytes(port, path, from, to)); } Future fetchContentAndResponse(VirtualDirectory virtualDir, String path, {int? from, int? to}) async { // if this is a fake test, then run the fake code path if (_isFakeTestExpando[currentTestCase]!) { var uri = _localhostUri(0, path); var request = FakeHttpRequest(uri, data: StreamController().stream); _addRangeHeader(request, from, to); var response = await _withFakeRequest(virtualDir, request); return [response.fakeContentBinary, response]; } assert(_isFakeTestExpando[currentTestCase] == false); return _withServer( virtualDir, (int port) => _fetchContentAndResponse(port, path, from, to)); } Future _withFakeRequest( VirtualDirectory virDir, FakeHttpRequest request) async { var value = await virDir.serveRequest(request); expect(value, isNull); expect(request.response.fakeDone, isTrue); var response = request.response; if (response.statusCode == HttpStatus.movedPermanently || response.statusCode == HttpStatus.movedTemporarily) { if (request.followRedirects == true) { var uri = Uri.parse(response.headers.value(HttpHeaders.locationHeader)!); var newMock = FakeHttpRequest(uri, followRedirects: true, data: StreamController().stream); return _withFakeRequest(virDir, newMock); } } return response; } Future _withServer( VirtualDirectory virDir, Future Function(int port) func) async { var server = await HttpServer.bind('localhost', 0); try { virDir.serve(server); return await func(server.port); } finally { await server.close(); } } Future _headers(int port, String path, int? from, int? to) async { var client = HttpClient(); try { var request = await client.get('localhost', port, path); _addRangeHeader(request, from, to); var response = await request.close(); await response.drain(); return response.headers; } finally { client.close(); } } Future _fetchAsString(int port, String path) async { var client = HttpClient(); try { var request = await client.get('localhost', port, path); var response = await request.close(); return await utf8.decodeStream(response.cast>()); } finally { client.close(); } } Future> _fetchAsBytes( int port, String path, int? from, int? to) async { var client = HttpClient(); try { var request = await client.get('localhost', port, path); _addRangeHeader(request, from, to); var response = await request.close(); return await response.fold([], (p, e) => p..addAll(e)); } finally { client.close(); } } Future _fetchContentAndResponse( int port, String path, int? from, int? to) async { var client = HttpClient(); try { var request = await client.get('localhost', port, path); _addRangeHeader(request, from, to); var response = await request.close(); var bytes = await response.fold>([], (p, e) => p..addAll(e)); return [bytes, response]; } finally { client.close(); } } Uri _localhostUri(int port, String path, {bool secure = false, bool rawPath = false}) { if (rawPath) { return Uri( scheme: secure ? 'https' : 'http', host: 'localhost', port: port, path: path); } else { return (secure ? Uri.https('localhost:$port', path) : Uri.http('localhost:$port', path)); } } void _addRangeHeader(request, int? from, int? to) { var fromStr = from != null ? '$from' : ''; var toStr = to != null ? '$to' : ''; if (fromStr.isNotEmpty || toStr.isNotEmpty) { request.headers.set(HttpHeaders.rangeHeader, 'bytes=$fromStr-$toStr'); } } void setupSecure() { var currentFileUri = (reflect(setupSecure) as ClosureMirror).function.location!.sourceUri; String localFile(String path) => currentFileUri.resolve(path).toFilePath(); serverContext = SecurityContext() ..useCertificateChain(localFile('certificates/server_chain.pem')) ..usePrivateKey(localFile('certificates/server_key.pem'), password: 'dartdart'); clientContext = SecurityContext() ..setTrustedCertificates(localFile('certificates/trusted_certs.pem')); }