credit_card_sanitizer 0.6.9 → 1.0.0

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.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/credit_card_sanitizer.rb +63 -45
  3. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c39e3333f13f9c1130b5f1b6f6499b7eff6c0dca6c8541e481f8d41c4686a7e9
4
- data.tar.gz: 69fa1024561a947ffebbad419e8cfe2fdaba89f218f6e6a5dbeade420df77cbf
3
+ metadata.gz: ed9cddb911074ee952f3f65184cd75c50b81376f5c57b6aa9d198f0bc5859aff
4
+ data.tar.gz: 735236460e25ffcb4f458046a664a174bfdffc460c6eebbbec2c9a950ef06697
5
5
  SHA512:
6
- metadata.gz: db8e96b0272a415c3300c2311ba458c6335df036c41e5aa5e0bb8bb8d6157fceca29a90740918526521f73be133910402ce65c470c3c9a6a669ee3fd6550824b
7
- data.tar.gz: a2a5d648461566fc101087c12603e361fdc3b38a026c9a920ca1a0ed83819b61d57ab441b63abfa91ad4fbd733b46dd773c13832d231bc106507e41851602ab4
6
+ metadata.gz: 2536400933a1b3c5ee9a6df3070eb31f4b2447a780c335a3429055fb046df072a416c34813d9e4bd00238beaac3db626a285080b7f7063d1af45b87ef29656b4
7
+ data.tar.gz: d9f623e2b39d0ca88430bc15407d664c40569b8802dc85c10ca2c3795a339f3b129f95fe0d120e6623eeca1c28f02a9203159bd42c2446fab4000bf8cdd5b6a5
@@ -1,39 +1,48 @@
1
- # encoding: utf-8
2
-
3
- require 'luhn_checksum'
4
- require 'securerandom'
5
- require 'tracking_number'
1
+ require "luhn_checksum"
2
+ require "securerandom"
3
+ require "tracking_number"
6
4
 
7
5
  class CreditCardSanitizer
8
- # https://github.com/Shopify/active_merchant/blob/master/lib/active_merchant/billing/credit_card_methods.rb#L5-L18
9
6
  CARD_COMPANIES = {
10
- 'visa' => /^4\d{12}(\d{3})?(\d{3})?$/,
11
- 'master' => /^(5[1-5]\d{4}|677189|222[1-9]\d{2}|22[3-9]\d{3}|2[3-6]\d{4}|27[01]\d{3}|2720\d{2})\d{10}$/,
12
- 'discover' => /^((6011|65\d{2}|64[4-9]\d)\d{12}|(62\d{14}))$/,
13
- 'american_express' => /^3[47]\d{13}$/,
14
- 'diners_club' => /^3(0[0-5]|[68]\d)\d{11}$/,
15
- 'jcb' => /^35(28|29|[3-8]\d)\d{12}$/,
16
- 'switch' => /^6759\d{12}(\d{2,3})?$/,
17
- 'solo' => /^6767\d{12}(\d{2,3})?$/,
18
- 'dankort' => /^5019\d{12}$/,
19
- 'maestro' => /^(5[06-8]|6\d)\d{10,17}$/,
20
- 'forbrugsforeningen' => /^600722\d{10}$/,
21
- 'laser' => /^(6304|6706|6709|6771(?!89))\d{8}(\d{4}|\d{6,7})?$/
7
+ "visa" => /^4\d{12}(\d{3})?(\d{3})?$/,
8
+ "master" => /^(5[1-5]\d{4}|677189|222[1-9]\d{2}|22[3-9]\d{3}|2[3-6]\d{4}|27[01]\d{3}|2720\d{2})\d{10}$/,
9
+ "discover" => /^((6011\d{12})|(65[4-9]\d{13})|(64[4-9]\d{13})|(622(?:12[6-9]|1[3-9]\d|[2-8]\d{2}|9[01]\d|92[0-5])\d{10}))$/,
10
+ "american_express" => /^3[47]\d{13}$/,
11
+ "diners_club" => /^3(0[0-5]|[68]\d)\d{11}$/,
12
+ "jcb" => /^35(28|29|[3-8]\d)\d{12}$/,
13
+ "switch" => /^(6759\d{12}(\d{2,3})?|(4903|4905|4911|4936|6333|6759)\d{12}|(4903|4905|4911|4936|6333|6759)\d{14}|(4903|4905|4911|4936|6333|6759)\d{15}|564182\d{10}|564182\d{12}|564182\d{13}|633110\d{10}|633110\d{12}|633110\d{13})$/,
14
+ "solo" => /^(6767\d{12}(\d{2,3})?|6334\d{12}|6334\d{14}|6334\d{15}|6767\d{14}|6767\d{15})$/,
15
+ "dankort" => /^5019\d{12}$/,
16
+ "maestro" => /^(5[06-8]\d{10,17}|6\d\d{10,17}|5018|5020|5038|5893|6304|6759|6761|6762|6763\d{8,15})$/,
17
+ "forbrugsforeningen" => /^600722\d{10}$/,
18
+ "laser" => /^(6304|6706|6709|6771(?!89))(\d{12,15}|\d{8}(\d{4}|\d{6,7})?)$/,
19
+ "bc_global" => /^(6541|6556)\d{12}$/,
20
+ "carte_blanche" => /^389\d{11}$/,
21
+ "insta_payment" => /^63[7-9]\d{13}$/,
22
+ "korean_local" => /^9\d{15}$/,
23
+ "union_pay" => /^62\d{14,17}$/,
24
+ "visa_master" => /^(4\d{12}(\d{3})?|5[1-5]\d{14})$/
22
25
  }.freeze
23
26
 
24
27
  CARD_NUMBER_GROUPINGS = {
25
- 'visa' => [[4, 4, 4, 4]],
26
- 'master' => [[4, 4, 4, 4]],
27
- 'discover' => [[4, 4, 4, 4]],
28
- 'american_express' => [[4, 6, 5]],
29
- 'diners_club' => [[4, 6, 4]],
30
- 'jcb' => [[4, 4, 4, 4]],
31
- 'switch' => [[4, 4, 4, 4]],
32
- 'solo' => [[4, 4, 4, 4]],
33
- 'dankort' => [[4, 4, 4, 4]],
34
- 'maestro' => [[4], [5]],
35
- 'forbrugsforeningen' => [[4, 4, 4, 4]],
36
- 'laser' => [[4, 4, 4, 4]]
28
+ "visa" => [[4, 4, 4, 4]],
29
+ "master" => [[4, 4, 4, 4]],
30
+ "discover" => [[4, 4, 4, 4]],
31
+ "american_express" => [[4, 6, 5]],
32
+ "diners_club" => [[4, 6, 4]],
33
+ "jcb" => [[4, 4, 4, 4]],
34
+ "switch" => [[4, 4, 4, 4]],
35
+ "solo" => [[4, 4, 4, 4], [4, 4, 4, 4, 2], [4, 4, 4, 4, 3]],
36
+ "dankort" => [[4, 4, 4, 4]],
37
+ "maestro" => [[4], [5], [4, 4, 4, 4], [4, 4, 4, 4, 1], [4, 4, 4, 4, 2], [4, 4, 4, 4, 3]],
38
+ "forbrugsforeningen" => [[4, 4, 4, 4]],
39
+ "laser" => [[4, 4, 4, 4], [4, 4, 4, 4, 1], [4, 4, 4, 4, 2], [4, 4, 4, 4, 3]],
40
+ "bc_global" => [[4, 4, 4, 4]],
41
+ "carte_blanche" => [[4, 6, 4]],
42
+ "insta_payment" => [[4, 4, 4, 4]],
43
+ "korean_local" => [[4, 4, 4, 4]],
44
+ "union_pay" => [[4, 4, 4, 4], [4, 4, 4, 4, 1], [4, 4, 4, 4, 2], [4, 4, 4, 4, 3]],
45
+ "visa_master" => [[4, 4, 4, 4], [4, 4, 4, 4, 3]]
37
46
  }.freeze
38
47
 
39
48
  ACCEPTED_PREFIX = /(?:cc|card|visa|amex)\z/i
@@ -44,11 +53,11 @@ class CreditCardSanitizer
44
53
  LINE_NOISE_CHAR = /[^\w\n,()&.\/:;<>]/
45
54
  LINE_NOISE = /#{LINE_NOISE_CHAR}{,5}/
46
55
  NONEMPTY_LINE_NOISE = /#{LINE_NOISE_CHAR}{1,5}/
47
- SCHEME_OR_PLUS = /((?:&#43;|\+|\/)|(?:[a-zA-Z][\-+.a-zA-Z\d]{,9}):[^\s>]+)/
56
+ SCHEME_OR_PLUS = /((?:&#43;|\+|\/)|(?:[a-zA-Z][-+.a-zA-Z\d]{,9}):[^\s>]+)/
48
57
  NUMBERS_WITH_LINE_NOISE = /#{SCHEME_OR_PLUS}?\d(?:#{LINE_NOISE}\d){10,30}/
49
58
 
50
59
  DEFAULT_OPTIONS = {
51
- replacement_token: '',
60
+ replacement_token: "",
52
61
  expose_first: 6,
53
62
  expose_last: 4,
54
63
  use_groupings: false,
@@ -88,31 +97,40 @@ class CreditCardSanitizer
88
97
  # sanitize!("I want all your credit card numbers!")
89
98
  # #=> nil
90
99
  #
91
- # Returns a String of the redacted text if a credit card number was detected.
92
- # Returns nil if no credit card numbers were detected.
100
+ # If options[:return_changes] is false, returns nil if no redaction happened,
101
+ # else the full text after redaction.
102
+ #
103
+ # If options[:return_changes] is true, returns nil if no redaction happened,
104
+ # else an array of [old_text, new_text] indicating what substrings were redacted.
93
105
  def sanitize!(text, options = {})
94
106
  options = @settings.merge(options)
95
107
 
96
108
  text.force_encoding(Encoding::UTF_8)
97
- text.scrub!('')
98
- redacted = nil
109
+ text.scrub!("")
110
+ changes = nil
99
111
 
100
112
  without_expiration(text) do
101
113
  text.gsub!(NUMBERS_WITH_LINE_NOISE) do |match|
102
114
  next match if $1
103
115
 
104
- candidate = Candidate.new(match, match.tr('^0-9', ''), $`, $')
116
+ candidate = Candidate.new(match, match.tr("^0-9", ""), $`, $')
105
117
 
106
118
  if valid_context?(candidate, options) && valid_numbers?(candidate, options)
107
- redacted = true
108
- redact_numbers(candidate, options)
119
+ redact_numbers(candidate, options).tap do |redacted_text|
120
+ changes ||= []
121
+ changes << [candidate.text, redacted_text]
122
+ end
109
123
  else
110
124
  match
111
125
  end
112
126
  end
113
127
  end
114
128
 
115
- redacted && text
129
+ if options[:return_changes]
130
+ changes
131
+ else
132
+ changes && text
133
+ end
116
134
  end
117
135
 
118
136
  # A proc that can be used
@@ -144,16 +162,16 @@ class CreditCardSanitizer
144
162
 
145
163
  def find_company(numbers)
146
164
  CARD_COMPANIES.each do |company, pattern|
147
- return company if numbers =~ pattern
165
+ return company if pattern.match?(numbers)
148
166
  end
149
167
  end
150
168
 
151
169
  def valid_grouping?(candidate, options)
152
170
  if options[:use_groupings]
153
- if company = find_company(candidate.numbers)
171
+ if (company = find_company(candidate.numbers))
154
172
  groupings = candidate.text.split(NONEMPTY_LINE_NOISE).map(&:length)
155
173
  return true if groupings.length == 1
156
- if company_groupings = CARD_NUMBER_GROUPINGS[company]
174
+ if (company_groupings = CARD_NUMBER_GROUPINGS[company])
157
175
  company_groupings.each do |company_grouping|
158
176
  return true if groupings.take(company_grouping.length) == company_grouping
159
177
  end
@@ -202,12 +220,12 @@ class CreditCardSanitizer
202
220
  end
203
221
 
204
222
  def without_expiration(text)
205
- expiration_date_boundary = SecureRandom.hex.tr('0123456789', 'ABCDEFGHIJ')
223
+ expiration_date_boundary = SecureRandom.hex.tr("0123456789", "ABCDEFGHIJ")
206
224
  text.gsub!(EXPIRATION_DATE) do |expiration_date|
207
225
  match = expiration_date.match(/(?<whitespace>\s*)(?<rest>.*)/m)
208
226
  "#{match[:whitespace]}#{expiration_date_boundary}#{match[:rest]}#{expiration_date_boundary}"
209
227
  end
210
228
  yield
211
- text.gsub!(expiration_date_boundary, '')
229
+ text.gsub!(expiration_date_boundary, "")
212
230
  end
213
231
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: credit_card_sanitizer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.9
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Eric Chapweske
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2022-06-02 00:00:00.000000000 Z
13
+ date: 2024-06-21 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: luhn_checksum
@@ -67,7 +67,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
67
67
  - !ruby/object:Gem::Version
68
68
  version: '0'
69
69
  requirements: []
70
- rubygems_version: 3.1.6
70
+ rubygems_version: 3.5.11
71
71
  signing_key:
72
72
  specification_version: 4
73
73
  summary: Credit card sanitizer