middleman/middleman-core/lib/middleman-core/preview_server/information.rb

274 lines
7.2 KiB
Ruby

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