From 867edb505b680c4aab2dbde7e2f1467bcf3bdc75 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Carric=CC=A7o?= Date: Fri, 21 Apr 2017 12:31:48 +0100 Subject: [PATCH 1/7] Extract timeout error to the service --- lib/public_ip.rb | 5 ++--- lib/public_ip/service/simple.rb | 3 ++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/public_ip.rb b/lib/public_ip.rb index 2a3d69d..2f2f389 100644 --- a/lib/public_ip.rb +++ b/lib/public_ip.rb @@ -13,7 +13,6 @@ module PublicIp TIMEOUT_IN_SECS = 3 class UnknownService < StandardError; end - class TimedOut < StandardError; end module_function @@ -24,11 +23,11 @@ def get_ip(service: :random) else PublicIp::Service::Registry[service].ip end - rescue Timeout::Error + rescue PublicIp::Service::TimedOut tries -= 1 retry if tries.positive? && service == :random - raise PublicIp::TimedOut, 'Took too long to get your public ip address, try with another service' + raise end def list_services diff --git a/lib/public_ip/service/simple.rb b/lib/public_ip/service/simple.rb index f2ac688..5d4c435 100644 --- a/lib/public_ip/service/simple.rb +++ b/lib/public_ip/service/simple.rb @@ -3,6 +3,7 @@ module PublicIp module Service + class TimedOut < StandardError; end class Simple attr_reader :uri attr_reader :headers @@ -27,7 +28,7 @@ def self.symbol end def self.perform_request - Timeout.timeout(PublicIp::TIMEOUT_IN_SECS) do + Timeout.timeout(PublicIp::TIMEOUT_IN_SECS, PublicIp::Service::TimedOut) do request = Net::HTTP::Get.new(uri, headers) http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = (uri.scheme == 'https') From 334a63d28dfda4f983b7bd29be53c52f30c71715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Carric=CC=A7o?= Date: Fri, 21 Apr 2017 12:32:27 +0100 Subject: [PATCH 2/7] Switch Mx Toolbox to a Plain service --- features/public_ip.feature | 2 +- lib/public_ip/service/mx_toolbox.rb | 8 ++------ 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/features/public_ip.feature b/features/public_ip.feature index ab31eb1..cb08268 100644 --- a/features/public_ip.feature +++ b/features/public_ip.feature @@ -33,7 +33,7 @@ Feature: PublicIp CLI application ip_info (http://ipinfo.io/ip) ip_ogre (http://ipogre.com) ipify (http://api.ipify.org/?format=json) - mx_toolbox (http://mxtoolbox.com/WhatIsMyIP/) + mx_toolbox (https://api.mxtoolbox.com/api/v1/utils/whatsmyip) private_internet_access (https://www.privateinternetaccess.com/pages/whats-my-ip/) smart_ip (http://smart-ip.net/myip) what_is_my_ip (https://www.whatismyip.com/) diff --git a/lib/public_ip/service/mx_toolbox.rb b/lib/public_ip/service/mx_toolbox.rb index 0dc0552..6865955 100644 --- a/lib/public_ip/service/mx_toolbox.rb +++ b/lib/public_ip/service/mx_toolbox.rb @@ -2,13 +2,9 @@ module PublicIp module Service - class MxToolbox < ParsedHTML + class MxToolbox < Plain def self.uri - URI('http://mxtoolbox.com/WhatIsMyIP/') - end - - def self.parse_ip_address(response_body) - Nokogiri::HTML(response_body).css('#ctl00_ContentPlaceHolder1_hlIP').text + URI('https://api.mxtoolbox.com/api/v1/utils/whatsmyip') end end end From 746c6ad72b8e531a0d4f25311933074435a7a3e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Carric=CC=A7o?= Date: Fri, 21 Apr 2017 12:34:14 +0100 Subject: [PATCH 3/7] =?UTF-8?q?Fail=20if=20it=E2=80=99s=20an=20invalid=20I?= =?UTF-8?q?P=20address=20(fixes=20#2)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/public_ip.rb | 2 +- lib/public_ip/service/json_type.rb | 4 +--- lib/public_ip/service/matched_expression.rb | 4 +--- lib/public_ip/service/parsed_html.rb | 6 ++++-- lib/public_ip/service/plain.rb | 6 ++---- lib/public_ip/service/simple.rb | 15 +++++++++++++++ public_ip.gemspec | 1 + 7 files changed, 25 insertions(+), 13 deletions(-) diff --git a/lib/public_ip.rb b/lib/public_ip.rb index 2f2f389..2c4e093 100644 --- a/lib/public_ip.rb +++ b/lib/public_ip.rb @@ -23,7 +23,7 @@ def get_ip(service: :random) else PublicIp::Service::Registry[service].ip end - rescue PublicIp::Service::TimedOut + rescue PublicIp::Service::TimedOut, PublicIp::Service::InvalidIpAddress tries -= 1 retry if tries.positive? && service == :random diff --git a/lib/public_ip/service/json_type.rb b/lib/public_ip/service/json_type.rb index 3ef7170..1649706 100644 --- a/lib/public_ip/service/json_type.rb +++ b/lib/public_ip/service/json_type.rb @@ -9,9 +9,7 @@ def self.parse_json(_json_data) raise 'Not implemented' end - def self.ip - response = perform_request - + def self.extract_ip(response) parse_json(JSON.parse(response.body.strip)) end end diff --git a/lib/public_ip/service/matched_expression.rb b/lib/public_ip/service/matched_expression.rb index 3f91943..2417030 100644 --- a/lib/public_ip/service/matched_expression.rb +++ b/lib/public_ip/service/matched_expression.rb @@ -5,9 +5,7 @@ class MatchedExpression < Simple attr_reader :match_regex - def self.ip - response = perform_request - + def self.extract_ip(response) response.body.match(match_regex)[1].strip end end diff --git a/lib/public_ip/service/parsed_html.rb b/lib/public_ip/service/parsed_html.rb index 3ba224d..231a723 100644 --- a/lib/public_ip/service/parsed_html.rb +++ b/lib/public_ip/service/parsed_html.rb @@ -3,9 +3,11 @@ module Service class ParsedHTML < Simple extend PublicIp::Service::Registrable - def self.ip - response = perform_request + def self.parse_ip_address(_html) + raise 'Not implemented' + end + def self.extract_ip(response) parse_ip_address(response.body) end end diff --git a/lib/public_ip/service/plain.rb b/lib/public_ip/service/plain.rb index 2d77279..74fedd0 100644 --- a/lib/public_ip/service/plain.rb +++ b/lib/public_ip/service/plain.rb @@ -3,10 +3,8 @@ module Service class Plain < Simple extend PublicIp::Service::Registrable - def self.ip - response = perform_request - - response.body.strip + def self.extract_ip(response) + response.body.strip.delete('"') end end end diff --git a/lib/public_ip/service/simple.rb b/lib/public_ip/service/simple.rb index 5d4c435..a1af783 100644 --- a/lib/public_ip/service/simple.rb +++ b/lib/public_ip/service/simple.rb @@ -1,13 +1,28 @@ require 'net/http' require 'timeout' +require 'ipaddress' module PublicIp module Service + class InvalidIpAddress < StandardError; end class TimedOut < StandardError; end + class Simple attr_reader :uri attr_reader :headers + def self.ip + response = perform_request + + ip_address = extract_ip(response) + + unless IPAddress.valid?(ip_address) + raise PublicIp::Service::InvalidIpAddress, "#{ip_address} is not a valid ip address" + end + + ip_address + end + def self.uri raise 'Not implemented' end diff --git a/public_ip.gemspec b/public_ip.gemspec index 7b0c99b..c30bc5f 100644 --- a/public_ip.gemspec +++ b/public_ip.gemspec @@ -27,6 +27,7 @@ Gem::Specification.new do |spec| spec.required_ruby_version = '>= 2.1' + spec.add_dependency 'ipaddress', '~> 0.8.3' spec.add_dependency 'methadone', '~> 1.9.2' spec.add_dependency 'nokogiri', '~> 1.8.1' From fe092ee26ad361d77c7cd574dbeddcbec235881c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Carric=CC=A7o?= Date: Sat, 3 Mar 2018 19:13:26 +0000 Subject: [PATCH 4/7] Remove IpOgre Service got discontinued --- features/public_ip.feature | 1 - lib/public_ip/service/ip_ogre.rb | 13 ------------- 2 files changed, 14 deletions(-) delete mode 100644 lib/public_ip/service/ip_ogre.rb diff --git a/features/public_ip.feature b/features/public_ip.feature index cb08268..6ed77ae 100644 --- a/features/public_ip.feature +++ b/features/public_ip.feature @@ -31,7 +31,6 @@ Feature: PublicIp CLI application ip_chicken (http://www.ipchicken.com/) ip_echo (http://ipecho.net/plain) ip_info (http://ipinfo.io/ip) - ip_ogre (http://ipogre.com) ipify (http://api.ipify.org/?format=json) mx_toolbox (https://api.mxtoolbox.com/api/v1/utils/whatsmyip) private_internet_access (https://www.privateinternetaccess.com/pages/whats-my-ip/) diff --git a/lib/public_ip/service/ip_ogre.rb b/lib/public_ip/service/ip_ogre.rb deleted file mode 100644 index 3828182..0000000 --- a/lib/public_ip/service/ip_ogre.rb +++ /dev/null @@ -1,13 +0,0 @@ -module PublicIp - module Service - class IpOgre < Plain - def self.uri - URI('http://ipogre.com') - end - - def self.headers - { 'User-Agent' => 'curl/7.9.8 (i686-pc-linux-gnu) libcurl 7.9.8 (OpenSSL 0.9.6b) (ipv6 enabled)' } - end - end - end -end From 3e20743588f5a888f41baee7fcc6ce4f3ff7f586 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Carric=CC=A7o?= Date: Sat, 3 Mar 2018 19:14:29 +0000 Subject: [PATCH 5/7] Add custom headers to services that block requests --- lib/public_ip/service/ifconfig_me.rb | 4 ++++ lib/public_ip/service/ip_echo.rb | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/lib/public_ip/service/ifconfig_me.rb b/lib/public_ip/service/ifconfig_me.rb index f1861d7..2ad93e7 100644 --- a/lib/public_ip/service/ifconfig_me.rb +++ b/lib/public_ip/service/ifconfig_me.rb @@ -4,6 +4,10 @@ class IfconfigMe < Plain def self.uri URI('http://ifconfig.me/ip') end + + def self.headers + { 'User-Agent' => 'curl/7.9.8 (i686-pc-linux-gnu) libcurl 7.9.8 (OpenSSL 0.9.6b) (ipv6 enabled)' } + end end end end diff --git a/lib/public_ip/service/ip_echo.rb b/lib/public_ip/service/ip_echo.rb index dda8f3c..70f07f7 100644 --- a/lib/public_ip/service/ip_echo.rb +++ b/lib/public_ip/service/ip_echo.rb @@ -4,6 +4,10 @@ class IpEcho < Plain def self.uri URI('http://ipecho.net/plain') end + + def self.headers + { 'User-Agent' => 'curl/7.9.8 (i686-pc-linux-gnu) libcurl 7.9.8 (OpenSSL 0.9.6b) (ipv6 enabled)' } + end end end end From 0e38e1e9083f62889be77c4cc5048cc1a83f6ad2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Carric=CC=A7o?= Date: Sat, 3 Mar 2018 19:15:25 +0000 Subject: [PATCH 6/7] Fix parsing ip address in the WhatIsMyIp service --- lib/public_ip/service/what_is_my_ip.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/public_ip/service/what_is_my_ip.rb b/lib/public_ip/service/what_is_my_ip.rb index 8dcb9b4..c4c0fa7 100644 --- a/lib/public_ip/service/what_is_my_ip.rb +++ b/lib/public_ip/service/what_is_my_ip.rb @@ -12,7 +12,9 @@ def self.headers end def self.parse_ip_address(response_body) - Nokogiri::HTML(response_body).css('.ip div').text + doc = Nokogiri::HTML(response_body).at('h3:contains("Your Public IPv4 is: ")') + + return doc.text.strip.sub('Your Public IPv4 is: ', '') unless doc.nil? end end end From ae8e6bc906650bce2b698350fda6abc2868c85ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Carric=CC=A7o?= Date: Sat, 3 Mar 2018 19:33:11 +0000 Subject: [PATCH 7/7] Test all the supported services --- Rakefile | 2 +- features/step_definitions/public_ip_steps.rb | 7 ++ features/support/env.rb | 3 - features/supported_services.feature | 94 ++++++++++++++++++++ public_ip.gemspec | 2 +- 5 files changed, 103 insertions(+), 5 deletions(-) create mode 100644 features/supported_services.feature diff --git a/Rakefile b/Rakefile index 39a60cd..9b57adf 100644 --- a/Rakefile +++ b/Rakefile @@ -5,7 +5,7 @@ require 'cucumber/rake/task' require 'rubocop/rake_task' Cucumber::Rake::Task.new(:features) do |t| - t.cucumber_opts = 'features --format pretty' + t.cucumber_opts = 'features --format pretty --retry 5' end RuboCop::RakeTask.new(:rubocop) diff --git a/features/step_definitions/public_ip_steps.rb b/features/step_definitions/public_ip_steps.rb index 5fe9e7a..8066ee7 100644 --- a/features/step_definitions/public_ip_steps.rb +++ b/features/step_definitions/public_ip_steps.rb @@ -1 +1,8 @@ # Put your step definitions here + +require 'ipaddress' + +Then(/^the output should be a valid ip address$/) do + puts last_command_started.output + expect(IPAddress.valid?(last_command_started.output)).to be_truthy +end diff --git a/features/support/env.rb b/features/support/env.rb index 1d81468..e4504ca 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,5 +1,2 @@ require 'aruba/cucumber' require 'methadone/cucumber' -require 'webmock/cucumber' - -WebMock.disable_net_connect! diff --git a/features/supported_services.feature b/features/supported_services.feature new file mode 100644 index 0000000..bb3c5bc --- /dev/null +++ b/features/supported_services.feature @@ -0,0 +1,94 @@ +Feature: PublicIp supported services + In order to make sure this CLI app works + I want to test every supported service + So that I'm sure that they work + + Scenario: Akamai + When I run `public_ip akamai` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: AWS + When I run `public_ip amazon_aws` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: Curl my IP + When I run `public_ip curl_my_ip` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: DynDNS + When I run `public_ip dyndns` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: I can haz IP + When I run `public_ip i_can_haz_ip` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: Ident Me + When I run `public_ip ident_me` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: Ip API + When I run `public_ip ip_api` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: Ifconfig Me + When I run `public_ip ifconfig_me` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: IP Chicken + When I run `public_ip ip_chicken` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: IP Echo + When I run `public_ip ip_echo` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: IP Info + When I run `public_ip ip_info` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: IPify + When I run `public_ip ipify` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: MxToolbox + When I run `public_ip mx_toolbox` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: Private Internet Access + When I run `public_ip private_internet_access` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: Smart IP + When I run `public_ip smart_ip` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: What is my IP + When I run `public_ip what_is_my_ip` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: What is my IP address + When I run `public_ip what_is_my_ip_address` + Then the exit status should be 0 + And the output should be a valid ip address + + Scenario: WTF is my IP + When I run `public_ip wtf_is_my_ip` + Then the exit status should be 0 + And the output should be a valid ip address \ No newline at end of file diff --git a/public_ip.gemspec b/public_ip.gemspec index c30bc5f..3d41268 100644 --- a/public_ip.gemspec +++ b/public_ip.gemspec @@ -31,7 +31,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'methadone', '~> 1.9.2' spec.add_dependency 'nokogiri', '~> 1.8.1' - spec.add_development_dependency 'aruba', '~> 0.11.2' + spec.add_development_dependency 'aruba', '~> 0.14.3' spec.add_development_dependency 'bundler', '~> 1.10' spec.add_development_dependency 'pry', '~> 0.10.3' spec.add_development_dependency 'rake', '~> 10.0'