187 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
			
		
		
	
	
			187 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Ruby
		
	
	
	
	
	
#
 | 
						|
# Author:: Seth Vargo <sethvargo@gmail.com>
 | 
						|
# Cookbook:: hostsfile
 | 
						|
# Library:: entry
 | 
						|
#
 | 
						|
# Copyright:: 2012-2013, Seth Vargo
 | 
						|
# Copyright:: 2012, CustomInk, LCC
 | 
						|
#
 | 
						|
# Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
# you may not use this file except in compliance with the License.
 | 
						|
# You may obtain a copy of the License at
 | 
						|
#
 | 
						|
#     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
#
 | 
						|
# Unless required by applicable law or agreed to in writing, software
 | 
						|
# distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
# See the License for the specific language governing permissions and
 | 
						|
# limitations under the License.
 | 
						|
#
 | 
						|
 | 
						|
require 'ipaddr'
 | 
						|
 | 
						|
# An object representation of a single line in a hostsfile.
 | 
						|
#
 | 
						|
# @author Seth Vargo <sethvargo@gmail.com>
 | 
						|
module HostsFile
 | 
						|
  class Entry
 | 
						|
    class << self
 | 
						|
      # Creates a new Hostsfile::Entry object by parsing a text line. The
 | 
						|
      # `line` attribute will be in the following format:
 | 
						|
      #
 | 
						|
      #     1.2.3.4 hostname [alias[, alias[, alias]]] [# comment [@priority]]
 | 
						|
      #
 | 
						|
      # @param [String] line
 | 
						|
      #   the line to parse
 | 
						|
      # @return [Entry]
 | 
						|
      #   a new entry object
 | 
						|
      def parse(line)
 | 
						|
        entry, comment = extract_comment(line)
 | 
						|
        comment, priority = extract_priority(comment)
 | 
						|
        entries = extract_entries(entry)
 | 
						|
 | 
						|
        # Return nil if the line is empty
 | 
						|
        return nil if entries.nil? || entries.empty?
 | 
						|
 | 
						|
        # If /etc/hosts has a broken content we throw a descriptive exception
 | 
						|
        if entries[0].nil?
 | 
						|
          raise ArgumentError, "/etc/hosts has a line without IP address: #{line}"
 | 
						|
        end
 | 
						|
        if entries[1].nil?
 | 
						|
          raise ArgumentError, "/etc/hosts has a line without hostname: #{line}"
 | 
						|
        end
 | 
						|
 | 
						|
        new(
 | 
						|
          ip_address: entries[0],
 | 
						|
          hostname:   entries[1],
 | 
						|
          aliases:    entries[2..-1],
 | 
						|
          comment:    comment,
 | 
						|
          priority:   priority
 | 
						|
        )
 | 
						|
      end
 | 
						|
 | 
						|
      private
 | 
						|
 | 
						|
      def extract_comment(line)
 | 
						|
        return nil if presence(line).nil?
 | 
						|
        line.split('#', 2).collect { |part| presence(part) }
 | 
						|
      end
 | 
						|
 | 
						|
      def extract_priority(comment)
 | 
						|
        return nil if comment.nil?
 | 
						|
 | 
						|
        if comment.include?('@')
 | 
						|
          comment.split('@', 2).collect { |part| presence(part) }
 | 
						|
        else
 | 
						|
          [comment, nil]
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def extract_entries(entry)
 | 
						|
        return nil if entry.nil?
 | 
						|
        entry.split(/\s+/).collect { |entry| presence(entry) }.compact
 | 
						|
      end
 | 
						|
 | 
						|
      def presence(string)
 | 
						|
        return nil if string.nil?
 | 
						|
        return nil if string.strip.empty?
 | 
						|
        string.strip
 | 
						|
      end
 | 
						|
    end
 | 
						|
 | 
						|
    # @return [String]
 | 
						|
    attr_accessor :ip_address, :hostname, :aliases, :comment, :priority
 | 
						|
 | 
						|
    # Creates a new entry from the given options.
 | 
						|
    #
 | 
						|
    # @param [Hash] options
 | 
						|
    #   a list of options to create the entry with
 | 
						|
    # @option options [String] :ip_address
 | 
						|
    #   the IP Address for this entry
 | 
						|
    # @option options [String] :hostname
 | 
						|
    #   the hostname for this entry
 | 
						|
    # @option options [String, Array<String>] :aliases
 | 
						|
    #   a alias or array of aliases for this entry
 | 
						|
    # @option options[String] :comment
 | 
						|
    #   an optional comment for this entry
 | 
						|
    # @option options [Integer] :priority
 | 
						|
    #   the relative priority of this entry (compared to others)
 | 
						|
    #
 | 
						|
    # @raise [ArgumentError]
 | 
						|
    #   if neither :ip_address nor :hostname are supplied
 | 
						|
    def initialize(options = {})
 | 
						|
      if options[:ip_address].nil? || options[:hostname].nil?
 | 
						|
        raise ArgumentError, ':ip_address and :hostname are both required options'
 | 
						|
      end
 | 
						|
 | 
						|
      @ip_address = IPAddr.new(remove_ip_scope(options[:ip_address]))
 | 
						|
      @hostname   = options[:hostname]
 | 
						|
      @aliases    = [options[:aliases]].flatten.compact
 | 
						|
      @comment    = options[:comment]
 | 
						|
      @priority   = options[:priority] || calculated_priority
 | 
						|
    end
 | 
						|
 | 
						|
    # Set a the new priority for an entry.
 | 
						|
    #
 | 
						|
    # @param [Integer] new_priority
 | 
						|
    #   the new priority to set
 | 
						|
    def priority=(new_priority)
 | 
						|
      @calculated_priority = false
 | 
						|
      @priority = new_priority
 | 
						|
    end
 | 
						|
 | 
						|
    # The line representation of this entry.
 | 
						|
    #
 | 
						|
    # @return [String]
 | 
						|
    #   the string representation of this entry
 | 
						|
    def to_line
 | 
						|
      hosts = [hostname, aliases].flatten.join(' ')
 | 
						|
 | 
						|
      comments = "# #{comment}".strip
 | 
						|
      comments << " @#{priority}" unless priority.nil? || @calculated_priority
 | 
						|
      comments = comments.strip
 | 
						|
      comments = nil if comments == '#'
 | 
						|
 | 
						|
      [ip_address, hosts, comments].compact.join("\t").strip
 | 
						|
    end
 | 
						|
 | 
						|
    # Returns true if priority is calculated
 | 
						|
    #
 | 
						|
    # @return [Boolean]
 | 
						|
    #   true if priority is calculated and false otherwise
 | 
						|
    def calculated_priority?
 | 
						|
      @calculated_priority
 | 
						|
    end
 | 
						|
 | 
						|
    private
 | 
						|
 | 
						|
    # Calculates the relative priority of this entry.
 | 
						|
    #
 | 
						|
    # @return [Integer]
 | 
						|
    #   the relative priority of this item
 | 
						|
    def calculated_priority
 | 
						|
      @calculated_priority = true
 | 
						|
 | 
						|
      return 82 if ip_address == IPAddr.new('127.0.0.1')
 | 
						|
      return 81 if ip_address == IPAddr.new('::1')
 | 
						|
      return 80 if IPAddr.new('127.0.0.0/8').include?(ip_address) # local
 | 
						|
      return 60 if ip_address.ipv4? # ipv4
 | 
						|
      return 20 if ip_address.ipv6? # ipv6
 | 
						|
      00
 | 
						|
    end
 | 
						|
 | 
						|
    # Removes the scopes pieces of the address, because reasons.
 | 
						|
    #
 | 
						|
    # @see https://bugs.ruby-lang.org/issues/8464
 | 
						|
    # @see https://github.com/customink-webops/hostsfile/issues/51
 | 
						|
    #
 | 
						|
    # @return [String, nil]
 | 
						|
    #
 | 
						|
    def remove_ip_scope(address)
 | 
						|
      return nil if address.nil?
 | 
						|
      address.to_s.sub(/%.*/, '')
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |