ip_contact_finder 1.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
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,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module IPContactFinder
4
+ VERSION_FILE_ROOT = File.expand_path('../../VERSION', __dir__)
5
+ VERSION = if File.file?(VERSION_FILE_ROOT)
6
+ File.read(VERSION_FILE_ROOT).strip.sub(/\Av/, '')
7
+ else
8
+ '0.0.0.dev'
9
+ end
10
+ 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: []