ip_contact_finder 1.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/lib/ip_contact_finder/entities.rb +23 -0
- data/lib/ip_contact_finder/entity.rb +45 -0
- data/lib/ip_contact_finder/error.rb +21 -0
- data/lib/ip_contact_finder/rdap.rb +78 -0
- data/lib/ip_contact_finder/version.rb +10 -0
- data/lib/ip_contact_finder.rb +61 -0
- metadata +205 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7d213095deacf2473debd978f225f195ab175d3baaf77f41db1a80140c5b84a8
|
4
|
+
data.tar.gz: d57a5d01dbd1ffae179702dc4ae93f695bb4f840819650e67dfa735fefcba474
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 78ca2c000c3bcda52f879453ef26d656cbe82f67c1bb04e4cdeae5b6f5519d14db7ccecc467b0f022863b346bd768136bbeda0ac5136b1001912e41cfa8f487d
|
7
|
+
data.tar.gz: 79899f7ed5c6b27987dec77a012fdbd91ae52bc5e58e5d74ed0a0193074f3f22175935e0beebeb2937d7a63572130f96a47c12f82a069e135e22ab8d87c873d1
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ip_contact_finder/entity'
|
4
|
+
|
5
|
+
module IPContactFinder
|
6
|
+
class Entities < Array
|
7
|
+
def initialize(entities)
|
8
|
+
super(entities.map { |e| Entity.new(e) })
|
9
|
+
end
|
10
|
+
|
11
|
+
# Return all the email addresses for this group of entities
|
12
|
+
#
|
13
|
+
# @return [Array<String>]
|
14
|
+
def email_addresses
|
15
|
+
addresses = []
|
16
|
+
each do |entity|
|
17
|
+
addresses |= entity.email_addresses
|
18
|
+
addresses |= entity.entities.email_addresses
|
19
|
+
end
|
20
|
+
addresses.sort
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ip_contact_finder/entities'
|
4
|
+
|
5
|
+
module IPContactFinder
|
6
|
+
class Entity
|
7
|
+
def initialize(entity)
|
8
|
+
@entity = entity
|
9
|
+
|
10
|
+
lookup_vcard_from_rdap if @entity['vcardArray'].nil?
|
11
|
+
end
|
12
|
+
|
13
|
+
# Return all sub entities within this one
|
14
|
+
#
|
15
|
+
# @return [IPContactFinder::Entities]
|
16
|
+
def entities
|
17
|
+
return Entities.new([]) if @entity['entities'].nil?
|
18
|
+
|
19
|
+
@entities ||= Entities.new(@entity['entities'])
|
20
|
+
end
|
21
|
+
|
22
|
+
# Return all email addresses for this entity
|
23
|
+
#
|
24
|
+
# @return [Array<String>]
|
25
|
+
def email_addresses
|
26
|
+
vcard = @entity['vcardArray']
|
27
|
+
return [] if vcard.nil?
|
28
|
+
return [] unless vcard[0] == 'vcard'
|
29
|
+
|
30
|
+
components = vcard[1]
|
31
|
+
version = components.find { |c| c[0] == 'version' }
|
32
|
+
return [] unless version.last == '4.0'
|
33
|
+
|
34
|
+
addresses = components.select { |c| c[0] == 'email' && c.last && c.last.length.positive? }
|
35
|
+
addresses.map { |a| a.last.downcase }
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def lookup_vcard_from_rdap
|
41
|
+
updated_entity = IPContactFinder.rdap.entity(@entity['handle'])
|
42
|
+
@entity['vcardArray'] = updated_entity['vcardArray']
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module IPContactFinder
|
4
|
+
class Error < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class TooManyRedirectsError < Error
|
8
|
+
end
|
9
|
+
|
10
|
+
class RateLimitedError < Error
|
11
|
+
end
|
12
|
+
|
13
|
+
class NotFoundError < Error
|
14
|
+
end
|
15
|
+
|
16
|
+
class RequestError < Error
|
17
|
+
end
|
18
|
+
|
19
|
+
class InvalidObjectError < Error
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'net/http'
|
4
|
+
require 'uri'
|
5
|
+
require 'json'
|
6
|
+
require 'ip_contact_finder/error'
|
7
|
+
|
8
|
+
module IPContactFinder
|
9
|
+
class RDAP
|
10
|
+
DEFAULT_SERVER = 'rdap.db.ripe.net'
|
11
|
+
|
12
|
+
attr_reader :server
|
13
|
+
attr_reader :logger
|
14
|
+
|
15
|
+
def initialize(server: DEFAULT_SERVER, logger: nil)
|
16
|
+
@server = server
|
17
|
+
@logger = logger
|
18
|
+
end
|
19
|
+
|
20
|
+
def lookup(type, object)
|
21
|
+
request(create_initial_url(type, object))
|
22
|
+
end
|
23
|
+
|
24
|
+
def ip(object)
|
25
|
+
lookup('ip', object)
|
26
|
+
end
|
27
|
+
|
28
|
+
def autnum(object)
|
29
|
+
lookup('autnum', object)
|
30
|
+
end
|
31
|
+
|
32
|
+
def entity(object)
|
33
|
+
lookup('entity', object)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def create_initial_url(type, object)
|
39
|
+
"https://#{@server}/#{type}/#{object}"
|
40
|
+
end
|
41
|
+
|
42
|
+
def request(uri, redirect_count: 0)
|
43
|
+
raise TooManyRedirectsError, 'Too many redirects' if redirect_count >= 5
|
44
|
+
|
45
|
+
uri = URI.parse(uri)
|
46
|
+
|
47
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
48
|
+
http.use_ssl = true
|
49
|
+
http.open_timeout = 10
|
50
|
+
http.read_timeout = 10
|
51
|
+
|
52
|
+
request = Net::HTTP::Get.new(uri.path)
|
53
|
+
request['User-Agent'] = "IP Contact Finder/#{IPContactFinder::VERSION}"
|
54
|
+
|
55
|
+
@logger&.info "Making request to #{uri}"
|
56
|
+
|
57
|
+
response = http.request(request)
|
58
|
+
@logger&.info "Got response #{response.code}"
|
59
|
+
|
60
|
+
case response
|
61
|
+
when Net::HTTPRedirection
|
62
|
+
new_location = response['Location']
|
63
|
+
request(new_location, redirect_count: redirect_count + 1)
|
64
|
+
when Net::HTTPOK
|
65
|
+
JSON.parse(response.body)
|
66
|
+
when Net::HTTPTooManyRequests
|
67
|
+
raise RateLimitedError
|
68
|
+
when Net::HTTPNotFound
|
69
|
+
raise NotFoundError
|
70
|
+
else
|
71
|
+
raise RequestError, "Got status #{response.code} (#{response.body})"
|
72
|
+
end
|
73
|
+
rescue SocketError, Errno::ECONNRESET, EOFError, Errno::EINVAL, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
|
74
|
+
Errno::ECONNREFUSED, OpenSSL::SSL::SSLError, Timeout::Error => e
|
75
|
+
raise RequestError, "#{e.message} (#{e.class})"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'ipaddr'
|
4
|
+
require 'ip_contact_finder/rdap'
|
5
|
+
require 'ip_contact_finder/entities'
|
6
|
+
|
7
|
+
module IPContactFinder
|
8
|
+
class << self
|
9
|
+
IPV4_REGEXP = /\A[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+\z/.freeze
|
10
|
+
IPV6_REGEXP = /\A[a-f0-9]+:[a-f0-9:]+\z/i.freeze
|
11
|
+
AUTNUM_REGEXP = /\A[0-9]+\z/.freeze
|
12
|
+
ENTITY_REGEXP = /\A[a-z\-_0-9]+\z/i.freeze
|
13
|
+
|
14
|
+
def rdap
|
15
|
+
@rdap ||= RDAP.new
|
16
|
+
end
|
17
|
+
|
18
|
+
def lookup(object)
|
19
|
+
case object
|
20
|
+
when AUTNUM_REGEXP
|
21
|
+
autnum(object)
|
22
|
+
when IPV4_REGEXP, IPV6_REGEXP
|
23
|
+
ip(object)
|
24
|
+
else
|
25
|
+
entity(object)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def ip(ip)
|
30
|
+
raise InvalidObjectError, "IP address given doesn't look at a valid IP address" unless valid_ip?(ip)
|
31
|
+
|
32
|
+
result = rdap.ip(ip)
|
33
|
+
Entities.new(result['entities']).email_addresses
|
34
|
+
end
|
35
|
+
|
36
|
+
def autnum(autnum)
|
37
|
+
raise InvalidObjectError, "Autnum given doesn't look at a valid autnum" unless autnum.match?(AUTNUM_REGEXP)
|
38
|
+
|
39
|
+
result = rdap.autnum(autnum)
|
40
|
+
Entities.new(result['entities']).email_addresses
|
41
|
+
end
|
42
|
+
|
43
|
+
def entity(entity)
|
44
|
+
raise InvalidObjectError, "Entity given doesn't look at a valid entity" unless entity.match?(ENTITY_REGEXP)
|
45
|
+
|
46
|
+
result = rdap.entity(entity)
|
47
|
+
Entities.new([result]).email_addresses
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def valid_ip?(ip)
|
53
|
+
return false unless ip.match?(IPV4_REGEXP) || ip.match?(IPV6_REGEXP)
|
54
|
+
|
55
|
+
IPAddr.new(ip)
|
56
|
+
true
|
57
|
+
rescue IPAddr::InvalidAddressError
|
58
|
+
false
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
metadata
ADDED
@@ -0,0 +1,205 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: ip_contact_finder
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0.rc1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Cooke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-05-19 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: json
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec-core
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: rspec-expectations
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rspec-mocks
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - '='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: 1.17.0
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - '='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: 1.17.0
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
- !ruby/object:Gem::Dependency
|
112
|
+
name: simplecov-console
|
113
|
+
requirement: !ruby/object:Gem::Requirement
|
114
|
+
requirements:
|
115
|
+
- - ">="
|
116
|
+
- !ruby/object:Gem::Version
|
117
|
+
version: '0'
|
118
|
+
type: :development
|
119
|
+
prerelease: false
|
120
|
+
version_requirements: !ruby/object:Gem::Requirement
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
version: '0'
|
125
|
+
- !ruby/object:Gem::Dependency
|
126
|
+
name: solargraph
|
127
|
+
requirement: !ruby/object:Gem::Requirement
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
type: :development
|
133
|
+
prerelease: false
|
134
|
+
version_requirements: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '0'
|
139
|
+
- !ruby/object:Gem::Dependency
|
140
|
+
name: vcr
|
141
|
+
requirement: !ruby/object:Gem::Requirement
|
142
|
+
requirements:
|
143
|
+
- - ">="
|
144
|
+
- !ruby/object:Gem::Version
|
145
|
+
version: '0'
|
146
|
+
type: :development
|
147
|
+
prerelease: false
|
148
|
+
version_requirements: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
- !ruby/object:Gem::Dependency
|
154
|
+
name: webmock
|
155
|
+
requirement: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
type: :development
|
161
|
+
prerelease: false
|
162
|
+
version_requirements: !ruby/object:Gem::Requirement
|
163
|
+
requirements:
|
164
|
+
- - ">="
|
165
|
+
- !ruby/object:Gem::Version
|
166
|
+
version: '0'
|
167
|
+
description: A Ruby library to resolve IP addresses and AS numbers to responsible
|
168
|
+
email addressses
|
169
|
+
email:
|
170
|
+
- adam@krystal.uk
|
171
|
+
executables: []
|
172
|
+
extensions: []
|
173
|
+
extra_rdoc_files: []
|
174
|
+
files:
|
175
|
+
- lib/ip_contact_finder.rb
|
176
|
+
- lib/ip_contact_finder/entities.rb
|
177
|
+
- lib/ip_contact_finder/entity.rb
|
178
|
+
- lib/ip_contact_finder/error.rb
|
179
|
+
- lib/ip_contact_finder/rdap.rb
|
180
|
+
- lib/ip_contact_finder/version.rb
|
181
|
+
homepage: https://github.com/krystal/ip_contact_finder
|
182
|
+
licenses:
|
183
|
+
- MIT
|
184
|
+
metadata: {}
|
185
|
+
post_install_message:
|
186
|
+
rdoc_options: []
|
187
|
+
require_paths:
|
188
|
+
- lib
|
189
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
190
|
+
requirements:
|
191
|
+
- - ">="
|
192
|
+
- !ruby/object:Gem::Version
|
193
|
+
version: '0'
|
194
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
195
|
+
requirements:
|
196
|
+
- - ">"
|
197
|
+
- !ruby/object:Gem::Version
|
198
|
+
version: 1.3.1
|
199
|
+
requirements: []
|
200
|
+
rubygems_version: 3.3.7
|
201
|
+
signing_key:
|
202
|
+
specification_version: 4
|
203
|
+
summary: A Ruby library to resolve IP addresses and AS numbers to responsible email
|
204
|
+
addressses
|
205
|
+
test_files: []
|