274 lines
7.2 KiB
Ruby
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
|