AsyncDNS-cr  settings.cr at [e71ccf3e48]

File src/settings.cr artifact 5585feb972 part of check-in e71ccf3e48


module AsyncDNS
  class Settings
    @local_domain : String?

    property static_hosts : Hash(String, Array(String))
    property nameservers : Array(String)
    property local_domain : String?
    property search_domains : Array(String)
    property uses_tcp : Bool
    property reload_period : Time::Span
    getter timeout : Time::Span
    getter max_attempts : Int32
    getter abs_num_dots : Int32

    def timeout=(timeout : Time::Span)
      if timeout < Time::Span.zero
        raise ArgumentError.new("timeout must be positive")
      end

      @timeout = timeout
    end

    def max_attempts=(max_attempts : Int32)
      if max_attempts < 0
        raise ArgumentError.new("max_attempts must be positive")
      end

      @max_attempts = max_attempts
    end

    def abs_num_dots=(abs_num_dots : Int32)
      if abs_num_dots < 0
        raise ArgumentError.new("abs_num_dots must be positive")
      end

      @abs_num_dots = abs_num_dots
    end

    def initialize
      @static_hosts = Hash(String, Array(String)).new
      @nameservers = [] of String
      @search_domains = [] of String
      @timeout = Time::Span.new(seconds: 2)
      @max_attempts = 3
      @abs_num_dots = 1
      @uses_tcp = false
      @reload_period = Time::Span.new(seconds: 2)
      @last_reload = Time::Span.zero

      reload
    end

    def reload
      return if Time.monotonic - @last_reload < @reload_period

      {% if flag?(:haiku) %}
        parse_hosts "/system/settings/network/hosts"
        parse_resolv_conf "/system/settings/network/resolv.conf"
      {% elsif flag?(:unix) %}
        parse_hosts "/etc/hosts"
        parse_resolv_conf "/etc/resolv.conf"
      {% else %}
        {% raise "Your OS is not supported by AsyncDNS" %}
      {% end %}

      @last_reload = Time.monotonic
    end

    private def parse_hosts(path)
      @static_hosts.clear

      File.each_line(path, chomp: true) do |line|
        pos = line.index('#')
        line = line[0, pos] if pos

        split = line.split(/[ \t]/, remove_empty: true)
        next if split.size < 2

        address = split[0]
        split[1, split.size - 1].each do |host|
          addresses = @static_hosts[host]?
          if addresses.nil?
            addresses = [] of String
            @static_hosts[host] = addresses
          end

          addresses << address
        end
      end
    end

    private def parse_resolv_conf(path)
      @nameservers.clear
      @local_domain = nil
      @search_domains.clear

      File.each_line(path, chomp: true) do |line|
        pos = line.index(/[#;]/)
        line = line[0, pos] if pos

        split = line.split(/[ \t]/, remove_empty: true)
        next if split.size < 2

        case split[0]
        when "nameserver"
          next if split.size != 2
          @nameservers << split[1]
        when "domain"
          next if split.size != 2
          @local_domain = split[1]
        when "search"
          @search_domains = split[1, split.size - 1]
        when "options"
          split[1, split.size - 1].each { |option| parse_resolv_option(option) }
        end
      end
    end

    private def parse_resolv_option(option)
      if option.starts_with?("ndots:")
        @abs_num_dots = option[6, option.size - 6].to_i
      elsif option.starts_with?("timeout:")
        @timeout = Time::Span.new(seconds: option[8, option.size - 8].to_i)
      elsif option.starts_with?("attempts:")
        @max_attempts = option[9, option.size - 9].to_i
      elsif option.starts_with?("reload-period:")
        @reload_period = Time::Span.new(
          seconds: option[14, option.size - 14].to_i)
      elsif option = "tcp"
        @uses_tcp = true
      end
    end
  end
end