Class: DomainSanity::Policy

Inherits:
Object
  • Object
show all
Defined in:
lib/domain_sanity/policy.rb

Overview

What counts as "valid" depends on who is asking. A certificate authority, a DNS zone editor, and a lenient form validator disagree about underscores, single-label names, private Public Suffix List entries, and script mixing.

Policy captures those choices as data instead of letting them accrete as boolean keyword arguments on every method. Pass a preset symbol, a Hash of overrides, or a Policy instance anywhere a policy: argument is accepted; Policy.coerce turns all three into a Policy.

DomainSanity.valid?("_dmarc.example.com", policy: :dns_zone)
DomainSanity.valid?("host", policy: { allow_single_label: true })

Policy only governs how a host name is judged; it never makes valid? accept an IP, wildcard, or reverse-zone subject (those are distinct kinds with their own predicates).

Constant Summary collapse

DEFAULTS =

Each field defaults to the strict (CA-style) choice: false, except allow_trailing_dot, since a single root dot denotes the same name.

{
  allow_underscore: false,         # permit "_" in labels (RFC 952/1123 forbid it)
  allow_single_label: false,       # permit a bare host with no TLD ("intranet")
  allow_trailing_dot: true,        # accept & normalize one trailing root dot
  include_private_suffixes: false, # treat private PSL entries (github.io) as suffixes
  allow_reserved_tld: false,       # accept RFC 6761/6762/7686 special-use TLDs (.test, .local, .onion, .internal)
  require_single_script: false     # reject confusable mixed-script IDN labels
}.freeze
FIELDS =
DEFAULTS.keys.freeze
PRESET_NAMES =

The names of the built-in presets.

%i[ca_baseline dns_zone lenient].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(**opts) ⇒ Policy

Returns a new instance of Policy.

Raises:

  • (ArgumentError)


38
39
40
41
42
43
44
# File 'lib/domain_sanity/policy.rb', line 38

def initialize(**opts)
  unknown = opts.keys - FIELDS
  raise ArgumentError, "unknown policy option(s): #{unknown.join(", ")}" unless unknown.empty?

  DEFAULTS.each { |field, default| instance_variable_set(:"@#{field}", opts.fetch(field, default)) }
  freeze
end

Class Method Details

.ca_baselineObject

Strict, certificate-authority-style defaults.



66
67
68
# File 'lib/domain_sanity/policy.rb', line 66

def ca_baseline
  new
end

.coerce(arg) ⇒ Object

Turn a Symbol preset, Hash of overrides, Policy, or nil into a Policy.



103
104
105
106
107
108
109
110
111
# File 'lib/domain_sanity/policy.rb', line 103

def coerce(arg)
  case arg
  when Policy then arg
  when Symbol then preset(arg)
  when Hash then ca_baseline.with(**arg)
  when nil then ca_baseline
  else raise ArgumentError, "cannot coerce #{arg.inspect} into a Policy"
  end
end

.dns_zoneObject

DNS zone editing: underscores, single-label hosts, and private suffixes are all fine here. Reserved special-use TLDs are still rejected - a real zone shouldn't contain them.



73
74
75
# File 'lib/domain_sanity/policy.rb', line 73

def dns_zone
  new(allow_underscore: true, allow_single_label: true, include_private_suffixes: true)
end

.lenientObject

Everything permissive, including RFC 6761/6762/7686 special-use TLDs (.test, .local, .onion, .internal). Script safety stays off to avoid false positives.



80
81
82
83
84
85
86
87
# File 'lib/domain_sanity/policy.rb', line 80

def lenient
  new(
    allow_underscore: true,
    allow_single_label: true,
    include_private_suffixes: true,
    allow_reserved_tld: true
  )
end

.preset(name) ⇒ Object



93
94
95
96
97
98
99
100
# File 'lib/domain_sanity/policy.rb', line 93

def preset(name)
  case name
  when :ca_baseline then ca_baseline
  when :dns_zone then dns_zone
  when :lenient then lenient
  else raise ArgumentError, "unknown policy preset: #{name.inspect}"
  end
end

.presetsObject



89
90
91
# File 'lib/domain_sanity/policy.rb', line 89

def presets
  PRESET_NAMES
end

Instance Method Details

#==(other) ⇒ Object Also known as: eql?



55
56
57
# File 'lib/domain_sanity/policy.rb', line 55

def ==(other)
  other.is_a?(Policy) && other.to_h == to_h
end

#hashObject



60
61
62
# File 'lib/domain_sanity/policy.rb', line 60

def hash
  to_h.hash
end

#to_hObject



51
52
53
# File 'lib/domain_sanity/policy.rb', line 51

def to_h
  FIELDS.to_h { |field| [field, public_send(field)] }
end

#with(**overrides) ⇒ Object

A copy with some fields overridden.



47
48
49
# File 'lib/domain_sanity/policy.rb', line 47

def with(**overrides)
  self.class.new(**to_h.merge(overrides))
end