// 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:io'; /// A utility for handling multiple hosts on multiple sources using a /// named-based approach. abstract class VirtualHost { /// The [Stream] of [HttpRequest] not matching any hosts. /// /// If this field is not read the default implementation will result in a /// [HttpStatus.forbidden] response. Stream get unhandled; /// Construct an [VirtualHost] with an optional initial source. /// /// The optional [source] is a shortcut for calling [addSource]. /// /// ## Example /// /// ```dart /// var server = await HttpServer.bind(..., 80); /// var virtualHost = VirtualHost(server); /// virtualServer.addHost('static.myserver.com').listen(...); /// virtualServer.addHost('cache.myserver.com').listen(...); /// ``` factory VirtualHost([Stream? source]) => _VirtualHost(source); /// Provide another source of [HttpRequest]s in the form of a [Stream]. void addSource(Stream source); /// Add a host to the [VirtualHost] instance. /// /// The host can be either a specific domain (`my.domain.name`) or a /// wildcard-based domain name (`*.domain.name`). The former will only match /// the specific domain name while the latter will match any series of /// sub-domains. /// /// If both `my.domain.name` and `*.domain.name` is specified, the most /// qualified will take precedence, `my.domain.name` in this case. Stream addHost(String host); } class _VirtualHostDomain { StreamController? any; StreamController? exact; Map subDomains = {}; } class _VirtualHost implements VirtualHost { final _VirtualHostDomain _topDomain = _VirtualHostDomain(); StreamController? _unhandledController; @override Stream get unhandled { _unhandledController ??= StreamController(); return _unhandledController!.stream; } _VirtualHost([Stream? source]) { if (source != null) addSource(source); } @override void addSource(Stream source) { source.listen((request) { var host = request.headers.host; if (host == null) { _unhandled(request); return; } var domains = host.split('.'); var current = _topDomain; StreamController? any; for (var i = domains.length - 1; i >= 0; i--) { if (current.any != null) any = current.any; if (i == 0) { var last = current.subDomains[domains[i]]; if (last != null && last.exact != null) { last.exact!.add(request); return; } } else { if (!current.subDomains.containsKey(domains[i])) { break; } current = current.subDomains[domains[i]]!; } } if (any != null) { any.add(request); return; } _unhandled(request); }); } @override Stream addHost(String host) { if (host.lastIndexOf('*') > 0) { throw ArgumentError( 'Wildcards are only allowed in the beginning of a host'); } var controller = StreamController(); var domains = host.split('.'); var current = _topDomain; for (var i = domains.length - 1; i >= 0; i--) { if (domains[i] == '*') { if (current.any != null) { throw ArgumentError('Host is already provided'); } current.any = controller; } else { if (!current.subDomains.containsKey(domains[i])) { current.subDomains[domains[i]] = _VirtualHostDomain(); } if (i > 0) { current = current.subDomains[domains[i]]!; } else { if (current.subDomains[domains[i]]!.exact != null) { throw ArgumentError('Host is already provided'); } current.subDomains[domains[i]]!.exact = controller; } } } return controller.stream; } void _unhandled(HttpRequest request) { if (_unhandledController != null) { _unhandledController!.add(request); return; } request.response.statusCode = HttpStatus.forbidden; request.response.close(); } }