diff --git a/fixtures/_experimentSound/localhost+2-key.pem b/fixtures/_experimentSound/localhost+2-key.pem new file mode 100644 index 000000000..7bc7c1e26 --- /dev/null +++ b/fixtures/_experimentSound/localhost+2-key.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDR8FlEhQ8XZctL +mGdx78Z5EInK2mcfU9Nq4oUKkPeiql7Sd3cd3prd+m5MYylL255wAZf8Fdupkko2 +cjQEsU24jUBWVG5q3YqZ+OidFYdxYnPbDhFtzL3Lr2zHGAi+MgqVENUF6ED2O5tU +9U1crceXmXHb3E8Sg+zXX6TMVCS11OH9VQ1ZutM0fFMPZxmfU5aOfd5kuTmTiaOt +OMyymroUPgzRW6UcbLfSdD7KuNFQgHDHiWkr2MtoZffgGM/cOfmCdgE85MdL6kyb +7TUTbAlTSpJNWwygObce7DN7qwM8HocRWSAfH+y3ewbs2NtzG1Az+a/kUBjhvmjT +WFiwDT+1AgMBAAECggEBALkJMXTeHh4OP2+ipVJb9r/P3tMnSornFEl5268jdNAv +f6HbX+a4xCDwUHUNVWGh8XRhQzcRgOllofl5EPYt3AXUoac1hZi1KStqooOJbTZ3 +gwvIy33OXl5/gM2+Fj6k1oTqMAej3FXq1Y69InGUTX4F5b/V3u+/zWlKyHK7mxuT +LIP3/iAxbpST5FB1G7ZPyz5mvzvEQFRiv9ubuMxYg3fzORULtbnnHNAM5jLG3IzZ +9EM/1umaeZu1bg9rGVVgr0l/rWMJSg5u+Z6jcrt9+Oj7hYrMPGHKHXAPhC1gaq+k +tlKHj/mhmJyN0jUXt71UgrsWF3MhrK2W2AlIfxXhIIECgYEA8oaIEOaeNmtDqlH5 +MmU0jd2EcMRnLrlfKs4fKObFoSMqELRB3yJ1u5Yt/wQCCSpN9bhkinR0l4Q7YsAy +4sg5Q3VaA/rUWkVKkv2sD04LbBmpHr5vksPnIPKcfAY3csQ1F5pu9gY6zgzdxBvN +nWEu8tqbUSdC5+0F+tB8IDW+/lUCgYEA3ZpVXmBLvgeZJ8rNjCCeKR5hXMYdFKqC +Yd5KfasTKH7h8a4GWo/mtUsS5UPRRjhrP5mPjdUKBFhSaJtX+k9Hhot58RN20fWR +4Ake4yV/cNFRykD1jsv9JJiPkggnVLVXN91+/EnKJozWv46q6nG44iX+q2w2i9FQ +6lohgwQp2+ECgYEAwKJY+0uiiUkD0woPbJb0emZj5wophvRYgfB80YkTmt0KcYAr +/icp6pjr6e3uDAedKrqOqWa8oQi3/sT45ibxTQKuQBEAkL8O79grzXBJJFDxgujy +SFnwgLwTzXNGoZL1NM1Gq4XhOX8Aut72n7Xsi5tV2MzdmMgsgr8MiK0ICo0CgYBA +h4yMauYjc/r5R2kLgQQNXTdk2JvnRK+q6Bww8/wkMq6AvfhDrtuztyTNdi4ekJdK +ceEHoB3GniGBLJs13JgrabocpVpYUXYlEwLXijfOFmYGy1u2NViFq5dDIvSxCg1X +yzwLI0GmcCSoq1bB5lO8Juw95skLdexmEdDoYfH+gQKBgDgaFlF9lYuKd0HhAxxm +oJ8WBv26FWxR8/UsvUPqI8Mzmb8J1Q22tZEMEVr8bN/HkcL+8DVQ4uh2zx9+rqC0 +aPqJZNgePGpRDhZ/cwA8xsg+Xkou8rrFjpGRuVuG9FvMEexufkHoDc94ppONuwsR +QzjTUxgXdLjtxT95ISwX83Nr +-----END PRIVATE KEY----- diff --git a/fixtures/_experimentSound/localhost+2.pem b/fixtures/_experimentSound/localhost+2.pem new file mode 100644 index 000000000..a62046b5e --- /dev/null +++ b/fixtures/_experimentSound/localhost+2.pem @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEgjCCAuqgAwIBAgIQBeTmjjlnyrCQUy06Xx/ybzANBgkqhkiG9w0BAQsFADCB +pTEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMT0wOwYDVQQLDDRMQVBU +T1AtQUFSSFFPMkxcV2Rlc3RATEFQVE9QLUFBUkhRTzJMIChXYWduZXIgRC4gRi4p +MUQwQgYDVQQDDDtta2NlcnQgTEFQVE9QLUFBUkhRTzJMXFdkZXN0QExBUFRPUC1B +QVJIUU8yTCAoV2FnbmVyIEQuIEYuKTAeFw0yNTAyMTQxNDI2MjFaFw0yNzA1MTQx +NDI2MjFaMGgxJzAlBgNVBAoTHm1rY2VydCBkZXZlbG9wbWVudCBjZXJ0aWZpY2F0 +ZTE9MDsGA1UECww0TEFQVE9QLUFBUkhRTzJMXFdkZXN0QExBUFRPUC1BQVJIUU8y +TCAoV2FnbmVyIEQuIEYuKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB +ANHwWUSFDxdly0uYZ3HvxnkQicraZx9T02rihQqQ96KqXtJ3dx3emt36bkxjKUvb +nnABl/wV26mSSjZyNASxTbiNQFZUbmrdipn46J0Vh3Fic9sOEW3MvcuvbMcYCL4y +CpUQ1QXoQPY7m1T1TVytx5eZcdvcTxKD7NdfpMxUJLXU4f1VDVm60zR8Uw9nGZ9T +lo593mS5OZOJo604zLKauhQ+DNFbpRxst9J0Psq40VCAcMeJaSvYy2hl9+AYz9w5 ++YJ2ATzkx0vqTJvtNRNsCVNKkk1bDKA5tx7sM3urAzwehxFZIB8f7Ld7BuzY23Mb +UDP5r+RQGOG+aNNYWLANP7UCAwEAAaNqMGgwDgYDVR0PAQH/BAQDAgWgMBMGA1Ud +JQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFLmYo+uZjtNZbL0jyR7XVnK/FICG +MCAGA1UdEQQZMBeCCWxvY2FsaG9zdIcEfwAAAYcEAAAAADANBgkqhkiG9w0BAQsF +AAOCAYEAbUhcggR4p8n/Us+ov8zvI+hBIrDi7GevWQQKrU1Rp2gzHV6glhqGMQ1A +BkGlj56L9O4P02awFZzB/55d2CsufzMD+d4aMKpdIJivwXByg6fJCtPOIprAiGeR +GuE9Q9ceUUJVrTYiy5CZeKTIQp4ZqWu5/wBQ+yvbsvxnzq2ESDpGmCkY6/ToMWmu +5cZPbZcyF7XBybSBdvaMGFsthRpawvQMsGHDZBaVhLn099Hjx2p35jNiM+HqCQXn +NweLYMJx9FZtOYyNJoTj0w97ViaSubz7V9n7LVIxW1QXmDXAk+75Fwq3x+n/tfhe +1BTLEOKvARvRVB6M1EE0z+zeNusHIu/TUMSmGv59OhsQdW5Lp1D0o3JK2mHu6tgP ++v1XRMaTQq73oHqU0oXCtsEdPuoFBNREBDhBOGB5wtFjfniu1zD0cKSDbVslSD5B +LKu9Yi1PzZTWVkaOWQolA6sYwWFLhQbHoyQoNjAJz58VNNtYIMjR6fZ0xTYiPQpJ +4Jh3TJKU +-----END CERTIFICATE----- diff --git a/webdev/CHANGELOG.md b/webdev/CHANGELOG.md index 6d0f66c0b..448a206af 100644 --- a/webdev/CHANGELOG.md +++ b/webdev/CHANGELOG.md @@ -1,6 +1,7 @@ ## 3.7.2-wip -- Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483) +- Adds `--offline` flag [#2483](https://github.com/dart-lang/webdev/pull/2483). +- Support the `--hostname` flag when the `--tls-cert-key` and `--tls-cert-chain` flags are present [#2588](https://github.com/dart-lang/webdev/pull/2588). ## 3.7.1 diff --git a/webdev/lib/src/serve/webdev_server.dart b/webdev/lib/src/serve/webdev_server.dart index edcfb848a..e166503f6 100644 --- a/webdev/lib/src/serve/webdev_server.dart +++ b/webdev/lib/src/serve/webdev_server.dart @@ -206,8 +206,8 @@ class WebDevServer { final serverContext = SecurityContext() ..useCertificateChain(tlsCertChain) ..usePrivateKey(tlsCertKey); - server = - await HttpMultiServer.loopbackSecure(options.port, serverContext); + server = await HttpMultiServer.bindSecure( + hostname, options.port, serverContext); } else { server = await HttpMultiServer.bind(hostname, options.port); } diff --git a/webdev/test/tls_test.dart b/webdev/test/tls_test.dart new file mode 100644 index 000000000..43a95e8e9 --- /dev/null +++ b/webdev/test/tls_test.dart @@ -0,0 +1,117 @@ +// Copyright (c) 2025, 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:io'; + +import 'package:collection/collection.dart'; +import 'package:logging/logging.dart'; +import 'package:path/path.dart' as p; +import 'package:test/test.dart'; +import 'package:test_descriptor/test_descriptor.dart' as d; +import 'package:test_process/test_process.dart'; + +import 'package:webdev/src/logging.dart'; +import 'package:webdev/src/serve/utils.dart'; + +import 'test_utils.dart'; + +void main() { + group('serve app with TLS options', () { + // Change to true for debugging. + final debug = false; + + final testRunner = TestRunner(); + late String exampleDirectory; + + setUpAll(() async { + configureLogWriter(debug); + await testRunner.setUpAll(); + exampleDirectory = + p.absolute(p.join(p.current, '..', 'fixtures', '_experimentSound')); + + final process = await TestProcess.start( + 'dart', + ['pub', 'upgrade'], + workingDirectory: exampleDirectory, + environment: getPubEnvironment(), + ); + + await process.shouldExit(0); + + await d + .file('.dart_tool/package_config.json', isNotEmpty) + .validate(exampleDirectory); + await d.file('pubspec.lock', isNotEmpty).validate(exampleDirectory); + }); + + test('listens on a loopback interface', () async { + final port = await findUnusedPort(); + final args = [ + 'serve', + 'web:$port', + '--hostname=0.0.0.0', + '--tls-cert-chain=localhost+2.pem', + '--tls-cert-key=localhost+2-key.pem', + ]; + + final process = + await testRunner.runWebDev(args, workingDirectory: exampleDirectory); + await expectLater(process.stdout, emitsThrough(contains('Succeeded'))); + + final client = HttpClient() + ..badCertificateCallback = (_, __, ___) => true; + try { + final request = + await client.getUrl(Uri.parse('https://localhost:$port')); + final response = await request.close(); + expect(response.statusCode, equals(200)); + } finally { + client.close(force: true); + } + + await process.kill(); + await process.shouldExit(); + }); + + test('listens on a non-loopback interface', () async { + final port = await findUnusedPort(); + final args = [ + 'serve', + 'web:$port', + '--hostname=0.0.0.0', + '--tls-cert-chain=localhost+2.pem', + '--tls-cert-key=localhost+2-key.pem', + ]; + + final process = + await testRunner.runWebDev(args, workingDirectory: exampleDirectory); + await expectLater(process.stdout, emitsThrough(contains('Succeeded'))); + + final interfaces = await NetworkInterface.list( + type: InternetAddressType.IPv4, + includeLoopback: false, + ); + final nonLoopback = interfaces.expand((i) => i.addresses).firstOrNull; + + if (nonLoopback == null) { + Logger.root.info( + 'No non-loopback IPv4 address available, skipping hostname test.'); + } else { + final client = HttpClient() + ..badCertificateCallback = (_, __, ___) => true; + try { + final request = await client + .getUrl(Uri.parse('https://${nonLoopback.address}:$port')); + final response = await request.close(); + expect(response.statusCode, equals(200)); + } finally { + client.close(force: true); + } + } + + await process.kill(); + await process.shouldExit(); + }); + }); +}