lib/middleman-core/preview_server/information.rb
require 'ipaddr' require 'active_support/core_ext/object/blank' require 'middleman-core/preview_server/checks' require 'middleman-core/preview_server/server_hostname' require 'middleman-core/preview_server/server_ip_address' module Middleman class PreviewServer # Basic information class to wrap common behaviour class BasicInformation private attr_reader :checks, :network_interfaces_inventory public attr_accessor :bind_address, :server_name, :port, :reason, :valid attr_reader :listeners, :site_addresses # Create instance # # @param [String] bind_address # The bind address of the server # # @param [String] server_name # The name of the server # # @param [Integer] port # The port to listen on def initialize(opts={}) @bind_address = ServerIpAddress.new(opts[:bind_address]) @server_name = ServerHostname.new(opts[:server_name]) @port = opts[:port] @valid = true @site_addresses = [] @listeners = [] @checks = [] # This needs to be check for each use case. Otherwise `Webrick` will # complain about that. @checks << Checks::InterfaceIsAvailableOnSystem.new end # Is the given information valid? def valid? valid == true end # Pass "self" to validator # # @param [#validate] validator # The validator def validate_me(validator) validator.validate self, checks end def resolve_me(*) fail NoMethodError end # Get network information # # @param [#network_interfaces] inventory # Get list of available network interfaces def show_me_network_interfaces(inventory) @network_interfaces_inventory = inventory end # Default is to get all network interfaces def local_network_interfaces network_interfaces_inventory.nil? ? [] : network_interfaces_inventory.network_interfaces(:all) end end # This only is used if no other parser is available # # The "default" behaviour is to fail because of "Checks::DenyAnyAny" class DefaultInformation < BasicInformation def initialize(*args) super # Make this fail @checks << Checks::DenyAnyAny.new end def resolve_me(*); end # Always true def self.matches?(*) true end end # This one is used if no bind address and no server name is given class AllInterfaces < BasicInformation def initialize(*args) super after_init end def self.matches?(opts={}) opts[:bind_address].blank? && opts[:server_name].blank? end # Resolve ips def resolve_me(resolver) hostname = ServerHostname.new(Socket.gethostname) hostname_ips = resolver.ips_for(hostname) network_interface = ServerIpAddress.new(Array(local_network_interfaces).first) resolved_name = ServerHostname.new(resolver.names_for(network_interface).first) if includes_array? local_network_interfaces, hostname_ips @server_name = hostname @site_addresses << hostname network_interface = ServerIpAddress.new((local_network_interfaces & hostname_ips).first) elsif RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ @server_name = hostname @site_addresses << hostname elsif !resolved_name.blank? @server_name = resolved_name @site_addresses << resolved_name else @server_name = network_interface end @site_addresses << network_interface self end private def includes_array?(a, b) !(a & b).empty? end def after_init @listeners << ServerIpAddress.new('::') @listeners << ServerIpAddress.new('0.0.0.0') end end # This is used if bind address is 0.0.0.0, the server name needs to be # blank class AllIpv4Interfaces < AllInterfaces def self.matches?(opts={}) opts[:bind_address] == '0.0.0.0' && opts[:server_name].blank? end # Use only ipv4 interfaces def local_network_interfaces network_interfaces_inventory.nil? ? [] : network_interfaces_inventory.network_interfaces(:ipv4) end private def after_init @listeners << ServerIpAddress.new('0.0.0.0') end end # This is used if bind address is ::, the server name needs to be blank class AllIpv6Interfaces < AllInterfaces def self.matches?(opts={}) opts[:bind_address] == '::' && opts[:server_name].blank? end # Use only ipv6 interfaces def local_network_interfaces network_interfaces_inventory.nil? ? [] : network_interfaces_inventory.network_interfaces(:ipv6) end private def after_init @listeners << ServerIpAddress.new('::') end end # Used if a bind address is given and the server name is blank class BindAddressInformation < BasicInformation def initialize(*args) super @listeners << bind_address @site_addresses << bind_address end def self.matches?(opts={}) !opts[:bind_address].blank? && opts[:server_name].blank? end # Resolv def resolve_me(resolver) @server_name = ServerHostname.new(resolver.names_for(bind_address).first) @site_addresses << @server_name unless @server_name.blank? self end end # Use if server name is given and bind address is blank class ServerNameInformation < BasicInformation def initialize(*args) super @checks << Checks::RequiresBindAddressIfServerNameIsGiven.new @site_addresses << server_name end def resolve_me(resolver) @bind_address = ServerIpAddress.new(resolver.ips_for(server_name).first) unless bind_address.blank? @listeners << bind_address @site_addresses << bind_address end self end def self.matches?(opts={}) opts[:bind_address].blank? && !opts[:server_name].blank? end end # Only used if bind address and server name are given and bind address is # not :: or 0.0.0.0 class BindAddressAndServerNameInformation < BasicInformation def initialize(*args) super @listeners << bind_address @site_addresses << server_name @site_addresses << bind_address @checks << Checks::ServerNameResolvesToBindAddress.new end def self.matches?(opts={}) !opts[:bind_address].blank? && !opts[:server_name].blank? && !%w(:: 0.0.0.0).include?(opts[:bind_address]) end def resolve_me(*); end end # If the server name is either an ipv4 or ipv6 address, e.g. 127.0.0.1 or # ::1, use this one class ServerNameIsIpInformation < BasicInformation def initialize(opts={}) super ip = ServerIpAddress.new(server_name.to_s) @listeners << ip @site_addresses << ip end def resolve_me(*); end def self.matches?(opts={}) ip = IPAddr.new(opts[:server_name]) ip.ipv4? || ip.ipv6? rescue false end end end end