Module: CompletionKit::ProviderEndpoint

Defined in:
app/services/completion_kit/provider_endpoint.rb

Defined Under Namespace

Classes: UnsafeEndpoint

Constant Summary collapse

BLOCKED_NETS =
[IPAddr.new("0.0.0.0/8"), IPAddr.new("100.64.0.0/10")].freeze
ISSUE_MESSAGES =
{
  invalid_url: "is not a valid http or https URL",
  unresolvable: "could not be resolved",
  unsafe_host: "resolves to a private address"
}.freeze

Class Method Summary collapse

Class Method Details

.addresses(host) ⇒ Object



54
55
56
57
58
59
# File 'app/services/completion_kit/provider_endpoint.rb', line 54

def addresses(host)
  bare = host.delete_prefix("[").delete_suffix("]").delete_suffix(".")
  [IPAddr.new(bare)]
rescue IPAddr::InvalidAddressError
  resolve(bare).map { |addr| IPAddr.new(addr) }
end

.issues_for(addrs) ⇒ Object



48
49
50
51
52
# File 'app/services/completion_kit/provider_endpoint.rb', line 48

def issues_for(addrs)
  return [:unresolvable] if addrs.empty?
  return [:unsafe_host] if addrs.any? { |ip| unsafe?(ip) }
  []
end

.parse(value) ⇒ Object



41
42
43
44
45
46
# File 'app/services/completion_kit/provider_endpoint.rb', line 41

def parse(value)
  uri = URI.parse(value.to_s.strip)
  uri if uri.is_a?(URI::HTTP) && uri.host.present?
rescue URI::InvalidURIError
  nil
end

.pin(builder, url) ⇒ Object



36
37
38
39
# File 'app/services/completion_kit/provider_endpoint.rb', line 36

def pin(builder, url)
  address = pinned_address(url)
  builder.adapter(:net_http) { |http| http.ipaddr = address }
end

.pinned_address(url) ⇒ Object

Raises:



27
28
29
30
31
32
33
34
# File 'app/services/completion_kit/provider_endpoint.rb', line 27

def pinned_address(url)
  uri = parse(url)
  raise UnsafeEndpoint, ISSUE_MESSAGES[:invalid_url] unless uri
  addrs = addresses(uri.host)
  issue = issues_for(addrs).first
  raise UnsafeEndpoint, ISSUE_MESSAGES[issue] if issue
  (addrs.find(&:ipv4?) || addrs.first).to_s
end

.resolve(host) ⇒ Object



61
62
63
64
65
# File 'app/services/completion_kit/provider_endpoint.rb', line 61

def resolve(host)
  Addrinfo.getaddrinfo(host, nil, nil, :STREAM).map(&:ip_address).uniq
rescue SocketError
  []
end

.safe?(url) ⇒ Boolean

Returns:

  • (Boolean)


23
24
25
# File 'app/services/completion_kit/provider_endpoint.rb', line 23

def safe?(url)
  validate(url).empty?
end

.unsafe?(ip) ⇒ Boolean

Returns:

  • (Boolean)


67
68
69
70
71
72
73
74
75
# File 'app/services/completion_kit/provider_endpoint.rb', line 67

def unsafe?(ip)
  ip = ip.native
  return true if ip.private?
  return true if ip.link_local?
  return true if ip.to_i.zero?
  return true if ip.ipv4? && BLOCKED_NETS.any? { |net| net.include?(ip) }
  return true if ip.loopback? && !CompletionKit.config.allow_loopback_endpoints
  false
end

.validate(url) ⇒ Object



17
18
19
20
21
# File 'app/services/completion_kit/provider_endpoint.rb', line 17

def validate(url)
  uri = parse(url)
  return [:invalid_url] unless uri
  issues_for(addresses(uri.host))
end