Skip to content

Commit

Permalink
[Fix rubocop#5973] Add Style/IpAddresses cop
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Vandersluis authored and bbatsov committed Jun 19, 2018
1 parent 659166e commit 4ed9aab
Show file tree
Hide file tree
Showing 9 changed files with 215 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ Style/FormatStringToken:
Exclude:
- spec/**/*

Style/IpAddresses:
# The test for this cop includes strings that would cause offenses
Exclude:
- spec/rubocop/cop/style/ip_addresses_spec.rb

Layout/EndOfLine:
EnforcedStyle: lf

Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### New features

* [#5973](https://github.com/bbatsov/rubocop/issues/5973): Add new `Style/IpAddresses` cop. ([@dvandersluis][])
* [#5843](https://github.com/bbatsov/rubocop/issues/5843): Add configuration options to `Naming/MemoizedInstanceVariableName` cop to allow leading underscores. ([@leklund][])
* [#5843](https://github.com/bbatsov/rubocop/issues/5843): Add `EnforcedStyleForLeadingUnderscores` to `Naming/MemoizedInstanceVariableName` cop to allow leading underscores. ([@leklund][])

Expand Down
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1139,6 +1139,12 @@ Style/InverseMethods:
:select: :reject
:select!: :reject!

Style/IpAddresses:
# Allow strings to be whitelisted
Whitelist:
- "::"
# :: is a valid IPv6 address, but could potentially be legitimately in code

Style/Lambda:
EnforcedStyle: line_count_dependent
SupportedStyles:
Expand Down
4 changes: 4 additions & 0 deletions config/disabled.yml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,10 @@ Style/InlineComment:
Description: 'Avoid trailing inline comments.'
Enabled: false

Style/IpAddresses:
Description: "Don't include literal IP addresses in code."
Enabled: false

Style/MethodCallWithArgsParentheses:
Description: 'Use parentheses for method calls with arguments.'
StyleGuide: '#method-invocation-parens'
Expand Down
1 change: 1 addition & 0 deletions lib/rubocop.rb
Original file line number Diff line number Diff line change
Expand Up @@ -441,6 +441,7 @@
require_relative 'rubocop/cop/style/infinite_loop'
require_relative 'rubocop/cop/style/inverse_methods'
require_relative 'rubocop/cop/style/inline_comment'
require_relative 'rubocop/cop/style/ip_addresses'
require_relative 'rubocop/cop/style/lambda'
require_relative 'rubocop/cop/style/lambda_call'
require_relative 'rubocop/cop/style/line_end_concatenation'
Expand Down
75 changes: 75 additions & 0 deletions lib/rubocop/cop/style/ip_addresses.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# frozen_string_literal: true

require 'resolv'

module RuboCop
module Cop
module Style
# This cop checks for hardcoded IP addresses, which can make code
# brittle. IP addresses are likely to need to be changed when code
# is deployed to a different server or environment, which may break
# a deployment if forgotten. Prefer setting IP addresses in ENV or
# other configuration.
#
# @example
#
# # bad
# ip_address = '127.59.241.29'
#
# # good
# ip_address = ENV['DEPLOYMENT_IP_ADDRESS']
class IpAddresses < Cop
include StringHelp

IPV6_MAX_SIZE = 45 # IPv4-mapped IPv6 is the longest
MSG = 'Do not hardcode IP addresses.'.freeze

def offense?(node)
contents = node.source[1...-1]

return false if whitelist.include?(contents.downcase)

# To try to avoid doing two regex checks on every string,
# shortcut out if the string does not look like an IP address
return false unless could_be_ip?(contents)

contents =~ ::Resolv::IPv4::Regex || contents =~ ::Resolv::IPv6::Regex
end

# Dummy implementation of method in ConfigurableEnforcedStyle that is
# called from StringHelp.
def opposite_style_detected; end

# Dummy implementation of method in ConfigurableEnforcedStyle that is
# called from StringHelp.
def correct_style_detected; end

private

def whitelist
whitelist = cop_config['Whitelist']
Array(whitelist).map(&:downcase)
end

def could_be_ip?(str)
# If the string is too long, it can't be an IP
return false if too_long?(str)

# If the string doesn't start with a colon or hexadecimal char,
# we know it's not an IP address
starts_with_hex_or_colon?(str)
end

def too_long?(str)
str.size > IPV6_MAX_SIZE
end

def starts_with_hex_or_colon?(str)
first_char = str[0].ord
(48..58).cover?(first_char) || (65..70).cover?(first_char) ||
(97..102).cover?(first_char)
end
end
end
end
end
1 change: 1 addition & 0 deletions manual/cops.md
Original file line number Diff line number Diff line change
Expand Up @@ -445,6 +445,7 @@ In the following section you find all available cops:
* [Style/InfiniteLoop](cops_style.md#styleinfiniteloop)
* [Style/InlineComment](cops_style.md#styleinlinecomment)
* [Style/InverseMethods](cops_style.md#styleinversemethods)
* [Style/IpAddresses](cops_style.md#styleipaddresses)
* [Style/Lambda](cops_style.md#stylelambda)
* [Style/LambdaCall](cops_style.md#stylelambdacall)
* [Style/LineEndConcatenation](cops_style.md#stylelineendconcatenation)
Expand Down
28 changes: 28 additions & 0 deletions manual/cops_style.md
Original file line number Diff line number Diff line change
Expand Up @@ -2552,6 +2552,34 @@ Name | Default value | Configurable values
InverseMethods | `{:any?=>:none?, :even?=>:odd?, :===>:!=, :=~=>:!~, :<=>:>=, :>=>:<=}` |
InverseBlocks | `{:select=>:reject, :select!=>:reject!}` |

## Style/IpAddresses

Enabled by default | Supports autocorrection
--- | ---
Disabled | No

This cop checks for hardcoded IP addresses, which can make code
brittle. IP addresses are likely to need to be changed when code
is deployed to a different server or environment, which may break
a deployment if forgotten. Prefer setting IP addresses in ENV or
other configuration.

### Examples

```ruby
# bad
ip_address = '127.59.241.29'

# good
ip_address = ENV['DEPLOYMENT_IP_ADDRESS']
```

### Configurable attributes

Name | Default value | Configurable values
--- | --- | ---
Whitelist | `::` | Array

## Style/Lambda

Enabled by default | Supports autocorrection
Expand Down
94 changes: 94 additions & 0 deletions spec/rubocop/cop/style/ip_addresses_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Style::IpAddresses, :config do
subject(:cop) { described_class.new(config) }

let(:cop_config) { {} }

context 'IPv4' do
it 'registers an offense for a valid address' do
expect_offense(<<-RUBY.strip_indent)
'255.255.255.255'
^^^^^^^^^^^^^^^^^ Do not hardcode IP addresses.
RUBY
end

it 'does not register an offense for an invalid address' do
expect_no_offenses('"578.194.591.059"')
end

it 'does not register an offense for an address inside larger text' do
expect_no_offenses('"My IP is 192.168.1.1"')
end
end

context 'IPv6' do
it 'registers an offense for a valid address' do
expect_offense(<<-RUBY.strip_indent)
'2001:0db8:85a3:0000:0000:8a2e:0370:7334'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not hardcode IP addresses.
RUBY
end

it 'registers an offense for an address with 0s collapsed' do
expect_offense(<<-RUBY.strip_indent)
'2001:db8:85a3::8a2e:370:7334'
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Do not hardcode IP addresses.
RUBY
end

it 'registers an offense for a shortened address' do
expect_offense(<<-RUBY.strip_indent)
'2001:db8::1'
^^^^^^^^^^^^^ Do not hardcode IP addresses.
RUBY
end

it 'registers an offense for a very short address' do
expect_offense(<<-RUBY.strip_indent)
'1::'
^^^^^ Do not hardcode IP addresses.
RUBY
end

it 'registers an offense for the loopback address' do
expect_offense(<<-RUBY.strip_indent)
'::1'
^^^^^ Do not hardcode IP addresses.
RUBY
end

it 'does not register an offense for an invalid address' do
expect_no_offenses('"2001:db8::1xyz"')
end

context 'the unspecified address :: (shortform of 0:0:0:0:0:0:0:0)' do
it 'does not register an offense' do
expect_no_offenses('"::"')
end

context 'when it is removed from the whitelist' do
let(:cop_config) { { 'Whitelist' => [] } }

it 'registers an offense' do
expect_offense(<<-RUBY.strip_indent)
'::'
^^^^ Do not hardcode IP addresses.
RUBY
end
end
end
end

context 'with whitelist' do
let(:cop_config) { { 'Whitelist' => ['a::b'] } }

it 'does not register an offense for a whitelisted address' do
expect_no_offenses('"a::b"')
end

it 'does not register an offense if the case differs' do
expect_no_offenses('"A::B"')
end
end
end

0 comments on commit 4ed9aab

Please sign in to comment.