encoded_id 1.0.0.rc5 → 1.0.0.rc6
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 +4 -4
- data/CHANGELOG.md +78 -2
- data/README.md +79 -333
- data/context/encoded_id.md +283 -0
- data/lib/encoded_id/alphabet.rb +32 -3
- data/lib/encoded_id/blocklist.rb +90 -0
- data/lib/encoded_id/encoders/base.rb +71 -0
- data/lib/encoded_id/encoders/hash_id.rb +531 -0
- data/lib/encoded_id/encoders/hash_id_consistent_shuffle.rb +110 -0
- data/lib/encoded_id/encoders/hash_id_ordinal_alphabet_separator_guards.rb +270 -0
- data/lib/encoded_id/encoders/hash_id_salt.rb +51 -0
- data/lib/encoded_id/encoders/my_sqids.rb +465 -0
- data/lib/encoded_id/encoders/sqids.rb +42 -0
- data/lib/encoded_id/hex_representation.rb +23 -5
- data/lib/encoded_id/reversible_id.rb +109 -23
- data/lib/encoded_id/version.rb +4 -1
- data/lib/encoded_id.rb +38 -5
- metadata +14 -23
- data/.devcontainer/Dockerfile +0 -9
- data/.devcontainer/compose.yml +0 -8
- data/.devcontainer/devcontainer.json +0 -8
- data/.standard.yml +0 -2
- data/Gemfile +0 -36
- data/Rakefile +0 -20
- data/Steepfile +0 -5
- data/ext/encoded_id/extconf.rb +0 -3
- data/ext/encoded_id/extension.c +0 -123
- data/ext/encoded_id/hashids.c +0 -939
- data/ext/encoded_id/hashids.h +0 -139
- data/lib/encoded_id/hash_id.rb +0 -227
- data/lib/encoded_id/hash_id_consistent_shuffle.rb +0 -27
- data/lib/encoded_id/hash_id_salt.rb +0 -15
- data/lib/encoded_id/ordinal_alphabet_separator_guards.rb +0 -90
- data/rbs_collection.yaml +0 -24
- data/sig/encoded_id.rbs +0 -189
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# EncodedId Ruby Gem - Technical Documentation
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`encoded_id` is a Ruby gem that provides reversible obfuscation of numerical and hexadecimal IDs into human-readable strings suitable for use in URLs. It offers a secure way to hide sequential database IDs from users while maintaining the ability to decode them back to their original values.
|
|
6
|
+
|
|
7
|
+
## Key Features
|
|
8
|
+
|
|
9
|
+
- **Reversible Encoding**: Unlike UUIDs, encoded IDs can be decoded back to their original numeric values
|
|
10
|
+
- **Multiple ID Support**: Encode multiple numeric IDs in a single string
|
|
11
|
+
- **Algorithm Choice**: Supports both HashIds and Sqids encoding algorithms
|
|
12
|
+
- **Human-Readable Format**: Character grouping and configurable separators for better readability
|
|
13
|
+
- **Character Mapping**: Handles easily confused characters (0/O, 1/I/l) through equivalence mapping
|
|
14
|
+
- **Performance Optimized**: Uses an optimized HashIds implementation for better performance
|
|
15
|
+
- **Profanity Protection**: Built-in blocklist support to prevent offensive words in generated IDs
|
|
16
|
+
- **Customizable**: Configurable alphabets, lengths, and formatting options
|
|
17
|
+
|
|
18
|
+
## Core API
|
|
19
|
+
|
|
20
|
+
### EncodedId::ReversibleId
|
|
21
|
+
|
|
22
|
+
The main class for encoding and decoding IDs.
|
|
23
|
+
|
|
24
|
+
#### Constructor
|
|
25
|
+
|
|
26
|
+
```ruby
|
|
27
|
+
EncodedId::ReversibleId.new(
|
|
28
|
+
salt:, # Required: String salt (min 4 chars)
|
|
29
|
+
length: 8, # Minimum length of encoded string
|
|
30
|
+
split_at: 4, # Split encoded string every X characters
|
|
31
|
+
split_with: "-", # Character to split with
|
|
32
|
+
alphabet: EncodedId::Alphabet.modified_crockford,
|
|
33
|
+
hex_digit_encoding_group_size: 4,
|
|
34
|
+
max_length: 128, # Maximum length limit
|
|
35
|
+
max_inputs_per_id: 32, # Maximum IDs to encode together
|
|
36
|
+
encoder: :hashids, # :hashids or :sqids
|
|
37
|
+
blocklist: nil # Words to prevent in IDs
|
|
38
|
+
)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
#### Key Methods
|
|
42
|
+
|
|
43
|
+
##### encode(values)
|
|
44
|
+
Encodes one or more integer IDs into an obfuscated string.
|
|
45
|
+
|
|
46
|
+
```ruby
|
|
47
|
+
coder = EncodedId::ReversibleId.new(salt: "my-salt")
|
|
48
|
+
|
|
49
|
+
# Single ID
|
|
50
|
+
coder.encode(123) # => "p5w9-z27j"
|
|
51
|
+
|
|
52
|
+
# Multiple IDs
|
|
53
|
+
coder.encode([78, 45]) # => "z2j7-0dmw"
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
##### decode(encoded_id, downcase: true)
|
|
57
|
+
Decodes an encoded string back to original IDs.
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
coder.decode("p5w9-z27j") # => [123]
|
|
61
|
+
coder.decode("z2j7-0dmw") # => [78, 45]
|
|
62
|
+
|
|
63
|
+
# Handles confused characters
|
|
64
|
+
coder.decode("p5w9-z27J") # => [123]
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
##### encode_hex(hex_strings) (Experimental)
|
|
68
|
+
Encodes hexadecimal strings (like UUIDs).
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
# Encode UUID
|
|
72
|
+
coder.encode_hex("9a566b8b-8618-42ab-8db7-a5a0276401fd")
|
|
73
|
+
# => "5jjy-c8d9-hxp2-qsve-rgh9-rxnt-7nb5-tve7-bf84-vr"
|
|
74
|
+
|
|
75
|
+
# With larger group size for shorter output
|
|
76
|
+
coder = EncodedId::ReversibleId.new(
|
|
77
|
+
salt: "my-salt",
|
|
78
|
+
hex_digit_encoding_group_size: 32
|
|
79
|
+
)
|
|
80
|
+
coder.encode_hex("9a566b8b-8618-42ab-8db7-a5a0276401fd")
|
|
81
|
+
# => "vr7m-qra8-m5y6-dkgj-5rqr-q44e-gp4a-52"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
##### decode_hex(encoded_id, downcase: true) (Experimental)
|
|
85
|
+
Decodes back to hexadecimal strings.
|
|
86
|
+
|
|
87
|
+
```ruby
|
|
88
|
+
coder.decode_hex("w72a-y0az") # => ["10f8c"]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### EncodedId::Alphabet
|
|
92
|
+
|
|
93
|
+
Class for creating custom alphabets.
|
|
94
|
+
|
|
95
|
+
#### Predefined Alphabets
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
# Default: modified Crockford Base32
|
|
99
|
+
# Characters: "0123456789abcdefghjkmnpqrstuvwxyz"
|
|
100
|
+
# Excludes: i, l, o, u (easily confused)
|
|
101
|
+
# Equivalences: {"o"=>"0", "i"=>"j", "l"=>"1", ...}
|
|
102
|
+
EncodedId::Alphabet.modified_crockford
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Custom Alphabets
|
|
106
|
+
|
|
107
|
+
```ruby
|
|
108
|
+
# Simple custom alphabet
|
|
109
|
+
alphabet = EncodedId::Alphabet.new("0123456789abcdef")
|
|
110
|
+
|
|
111
|
+
# With character equivalences
|
|
112
|
+
alphabet = EncodedId::Alphabet.new(
|
|
113
|
+
"0123456789ABCDEF",
|
|
114
|
+
{"a"=>"A", "b"=>"B", "c"=>"C", "d"=>"D", "e"=>"E", "f"=>"F"}
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
# Greek alphabet example
|
|
118
|
+
alphabet = EncodedId::Alphabet.new("αβγδεζηθικλμνξοπρστυφχψω")
|
|
119
|
+
coder = EncodedId::ReversibleId.new(salt: "my-salt", alphabet: alphabet)
|
|
120
|
+
coder.encode(123) # => "θεαψ-ζκυο"
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Configuration Options
|
|
124
|
+
|
|
125
|
+
### Basic Options
|
|
126
|
+
|
|
127
|
+
- **salt**: Required secret salt (minimum 4 characters). Changing the salt changes all encoded IDs
|
|
128
|
+
- **length**: Minimum length of encoded string (default: 8)
|
|
129
|
+
- **max_length**: Maximum allowed length (default: 128) to prevent DoS attacks
|
|
130
|
+
- **max_inputs_per_id**: Maximum IDs encodable together (default: 32)
|
|
131
|
+
|
|
132
|
+
### Encoder Selection
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# Default HashIds encoder
|
|
136
|
+
coder = EncodedId::ReversibleId.new(salt: "my-salt")
|
|
137
|
+
|
|
138
|
+
# Sqids encoder (requires 'sqids' gem)
|
|
139
|
+
coder = EncodedId::ReversibleId.new(salt: "my-salt", encoder: :sqids)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Important**: HashIds and Sqids produce different encodings and are not compatible.
|
|
143
|
+
|
|
144
|
+
### Formatting Options
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
# Custom splitting
|
|
148
|
+
coder = EncodedId::ReversibleId.new(
|
|
149
|
+
salt: "my-salt",
|
|
150
|
+
split_at: 3, # Group every 3 chars
|
|
151
|
+
split_with: "." # Use dots
|
|
152
|
+
)
|
|
153
|
+
coder.encode(123) # => "p5w.9z2.7j"
|
|
154
|
+
|
|
155
|
+
# No splitting
|
|
156
|
+
coder = EncodedId::ReversibleId.new(
|
|
157
|
+
salt: "my-salt",
|
|
158
|
+
split_at: nil
|
|
159
|
+
)
|
|
160
|
+
coder.encode(123) # => "p5w9z27j"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Blocklist Configuration
|
|
164
|
+
|
|
165
|
+
```ruby
|
|
166
|
+
# Prevent specific words
|
|
167
|
+
coder = EncodedId::ReversibleId.new(
|
|
168
|
+
salt: "my-salt",
|
|
169
|
+
blocklist: ["bad", "offensive", "words"]
|
|
170
|
+
)
|
|
171
|
+
|
|
172
|
+
# Behavior differs by encoder:
|
|
173
|
+
# - HashIds: Raises error if blocklisted word appears
|
|
174
|
+
# - Sqids: Automatically avoids generating blocklisted words
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
## Exception Handling
|
|
178
|
+
|
|
179
|
+
| Exception | Description |
|
|
180
|
+
|-----------|-------------|
|
|
181
|
+
| `EncodedId::InvalidConfigurationError` | Invalid configuration parameters |
|
|
182
|
+
| `EncodedId::InvalidAlphabetError` | Invalid alphabet (< 16 unique chars) |
|
|
183
|
+
| `EncodedId::EncodedIdFormatError` | Invalid encoded ID format |
|
|
184
|
+
| `EncodedId::EncodedIdLengthError` | Encoded ID exceeds max_length |
|
|
185
|
+
| `EncodedId::InvalidInputError` | Invalid input (negative integers, too many inputs) |
|
|
186
|
+
| `EncodedId::SaltError` | Invalid salt (too short) |
|
|
187
|
+
|
|
188
|
+
## Usage Examples
|
|
189
|
+
|
|
190
|
+
### Basic Usage
|
|
191
|
+
```ruby
|
|
192
|
+
# Initialize
|
|
193
|
+
coder = EncodedId::ReversibleId.new(salt: "my-secret-salt")
|
|
194
|
+
|
|
195
|
+
# Encode/decode cycle
|
|
196
|
+
encoded = coder.encode(123) # => "p5w9-z27j"
|
|
197
|
+
decoded = coder.decode(encoded) # => [123]
|
|
198
|
+
original_id = decoded.first # => 123
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Multiple IDs
|
|
202
|
+
```ruby
|
|
203
|
+
# Encode multiple IDs in one string
|
|
204
|
+
encoded = coder.encode([78, 45, 92]) # => "z2j7-0dmw-kf8p"
|
|
205
|
+
decoded = coder.decode(encoded) # => [78, 45, 92]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### Custom Configuration
|
|
209
|
+
```ruby
|
|
210
|
+
# Highly customized instance
|
|
211
|
+
coder = EncodedId::ReversibleId.new(
|
|
212
|
+
salt: "my-app-salt",
|
|
213
|
+
encoder: :sqids,
|
|
214
|
+
length: 12,
|
|
215
|
+
split_at: 3,
|
|
216
|
+
split_with: ".",
|
|
217
|
+
alphabet: EncodedId::Alphabet.new("0123456789ABCDEF"),
|
|
218
|
+
blocklist: ["BAD", "FAKE"]
|
|
219
|
+
)
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
### Hex Encoding (UUIDs)
|
|
223
|
+
```ruby
|
|
224
|
+
# For encoding UUIDs efficiently
|
|
225
|
+
coder = EncodedId::ReversibleId.new(
|
|
226
|
+
salt: "my-salt",
|
|
227
|
+
hex_digit_encoding_group_size: 32
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
uuid = "550e8400-e29b-41d4-a716-446655440000"
|
|
231
|
+
encoded = coder.encode_hex(uuid)
|
|
232
|
+
decoded = coder.decode_hex(encoded).first # => original UUID
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
## Performance Considerations
|
|
236
|
+
|
|
237
|
+
1. **Algorithm Choice**:
|
|
238
|
+
- HashIds: Faster encoding, especially with blocklists
|
|
239
|
+
- Sqids: Faster decoding
|
|
240
|
+
|
|
241
|
+
2. **Blocklist Impact**: Large blocklists can slow down encoding, especially with Sqids
|
|
242
|
+
|
|
243
|
+
3. **Length vs Performance**: Longer minimum lengths may require more computation
|
|
244
|
+
|
|
245
|
+
4. **Memory Usage**: The gem uses optimized implementations to minimize memory allocation
|
|
246
|
+
|
|
247
|
+
## Security Notes
|
|
248
|
+
|
|
249
|
+
**Important**: Encoded IDs are NOT cryptographically secure. They provide obfuscation, not encryption. Do not rely on them for security purposes. They can potentially be reversed through brute-force attacks if the salt is compromised.
|
|
250
|
+
|
|
251
|
+
Use encoded IDs for:
|
|
252
|
+
- Hiding sequential database IDs
|
|
253
|
+
- Creating user-friendly URLs
|
|
254
|
+
- Preventing ID enumeration attacks
|
|
255
|
+
|
|
256
|
+
Do NOT use for:
|
|
257
|
+
- Secure tokens
|
|
258
|
+
- Authentication
|
|
259
|
+
- Sensitive data protection
|
|
260
|
+
|
|
261
|
+
## Installation
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
# Gemfile
|
|
265
|
+
gem 'encoded_id'
|
|
266
|
+
|
|
267
|
+
# Or install directly
|
|
268
|
+
gem install encoded_id
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
For Sqids support:
|
|
272
|
+
```ruby
|
|
273
|
+
gem 'encoded_id'
|
|
274
|
+
gem 'sqids'
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## Best Practices
|
|
278
|
+
|
|
279
|
+
1. **Salt Management**: Use a strong, unique salt and store it securely (e.g., environment variables)
|
|
280
|
+
2. **Consistent Configuration**: Once in production, don't change salt or encoder
|
|
281
|
+
3. **Error Handling**: Always handle potential exceptions when decoding user input
|
|
282
|
+
4. **Length Limits**: Set appropriate max_length to prevent DoS attacks
|
|
283
|
+
5. **Validation**: Validate decoded IDs before using them in database queries
|
data/lib/encoded_id/alphabet.rb
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
# rbs_inline: enabled
|
|
4
|
+
|
|
3
5
|
module EncodedId
|
|
4
6
|
class Alphabet
|
|
5
7
|
MIN_UNIQUE_CHARACTERS = 16
|
|
6
8
|
|
|
7
9
|
class << self
|
|
10
|
+
# @rbs return: Alphabet
|
|
8
11
|
def modified_crockford
|
|
9
12
|
new(
|
|
10
13
|
"0123456789abcdefghjkmnpqrstuvwxyz",
|
|
@@ -16,15 +19,21 @@ module EncodedId
|
|
|
16
19
|
)
|
|
17
20
|
end
|
|
18
21
|
|
|
22
|
+
# @rbs return: Alphabet
|
|
19
23
|
def alphanum
|
|
20
24
|
new("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890")
|
|
21
25
|
end
|
|
22
26
|
end
|
|
23
27
|
|
|
28
|
+
# @rbs @unique_characters: Array[String]
|
|
29
|
+
# @rbs @characters: String
|
|
30
|
+
# @rbs @equivalences: Hash[String, String]?
|
|
31
|
+
|
|
32
|
+
# @rbs (String | Array[String] characters, ?Hash[String, String]? equivalences) -> void
|
|
24
33
|
def initialize(characters, equivalences = nil)
|
|
25
34
|
raise_invalid_alphabet! unless valid_input_characters?(characters)
|
|
26
35
|
@unique_characters = unique_character_alphabet(characters)
|
|
27
|
-
|
|
36
|
+
raise_invalid_characters! unless valid_characters?
|
|
28
37
|
raise_character_set_too_small! unless sufficient_characters?
|
|
29
38
|
raise_invalid_equivalences! unless valid_equivalences?(equivalences)
|
|
30
39
|
|
|
@@ -32,24 +41,31 @@ module EncodedId
|
|
|
32
41
|
@equivalences = equivalences
|
|
33
42
|
end
|
|
34
43
|
|
|
35
|
-
attr_reader :unique_characters
|
|
44
|
+
attr_reader :unique_characters #: Array[String]
|
|
45
|
+
attr_reader :characters #: String
|
|
46
|
+
attr_reader :equivalences #: Hash[String, String]?
|
|
36
47
|
|
|
48
|
+
# @rbs (String character) -> bool
|
|
37
49
|
def include?(character)
|
|
38
50
|
unique_characters.include?(character)
|
|
39
51
|
end
|
|
40
52
|
|
|
53
|
+
# @rbs return: Array[String]
|
|
41
54
|
def to_a
|
|
42
55
|
unique_characters.dup
|
|
43
56
|
end
|
|
44
57
|
|
|
58
|
+
# @rbs return: String
|
|
45
59
|
def to_s
|
|
46
60
|
@characters.dup
|
|
47
61
|
end
|
|
48
62
|
|
|
63
|
+
# @rbs return: String
|
|
49
64
|
def inspect
|
|
50
65
|
"#<#{self.class.name} chars: #{unique_characters.inspect}>"
|
|
51
66
|
end
|
|
52
67
|
|
|
68
|
+
# @rbs return: Integer
|
|
53
69
|
def size
|
|
54
70
|
unique_characters.size
|
|
55
71
|
end
|
|
@@ -57,23 +73,28 @@ module EncodedId
|
|
|
57
73
|
|
|
58
74
|
private
|
|
59
75
|
|
|
76
|
+
# @rbs (String | Array[String] characters) -> bool
|
|
60
77
|
def valid_input_characters?(characters)
|
|
61
78
|
return false unless characters.is_a?(Array) || characters.is_a?(String)
|
|
62
79
|
characters.size > 0
|
|
63
80
|
end
|
|
64
81
|
|
|
82
|
+
# @rbs (String | Array[String] characters) -> Array[String]
|
|
65
83
|
def unique_character_alphabet(characters)
|
|
66
84
|
(characters.is_a?(Array) ? characters : characters.chars).uniq
|
|
67
85
|
end
|
|
68
86
|
|
|
87
|
+
# @rbs return: bool
|
|
69
88
|
def valid_characters?
|
|
70
89
|
unique_characters.size > 0 && unique_characters.grep(/\s|\0/).size == 0
|
|
71
90
|
end
|
|
72
91
|
|
|
92
|
+
# @rbs return: bool
|
|
73
93
|
def sufficient_characters?
|
|
74
94
|
unique_characters.size >= MIN_UNIQUE_CHARACTERS
|
|
75
95
|
end
|
|
76
96
|
|
|
97
|
+
# @rbs (Hash[String, String]? equivalences) -> bool
|
|
77
98
|
def valid_equivalences?(equivalences)
|
|
78
99
|
return true if equivalences.nil?
|
|
79
100
|
return false unless equivalences.is_a?(Hash)
|
|
@@ -82,14 +103,22 @@ module EncodedId
|
|
|
82
103
|
(unique_characters & equivalences.keys).empty? && (equivalences.values - unique_characters).empty?
|
|
83
104
|
end
|
|
84
105
|
|
|
106
|
+
# @rbs return: void
|
|
85
107
|
def raise_invalid_alphabet!
|
|
86
|
-
raise InvalidAlphabetError, "Alphabet must be a string or array
|
|
108
|
+
raise InvalidAlphabetError, "Alphabet must be a populated string or array"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @rbs return: void
|
|
112
|
+
def raise_invalid_characters!
|
|
113
|
+
raise InvalidAlphabetError, "Alphabet must not contain whitespace or null characters."
|
|
87
114
|
end
|
|
88
115
|
|
|
116
|
+
# @rbs return: void
|
|
89
117
|
def raise_character_set_too_small!
|
|
90
118
|
raise InvalidAlphabetError, "Alphabet must contain at least #{MIN_UNIQUE_CHARACTERS} unique characters."
|
|
91
119
|
end
|
|
92
120
|
|
|
121
|
+
# @rbs return: void
|
|
93
122
|
def raise_invalid_equivalences!
|
|
94
123
|
raise InvalidConfigurationError, "Character equivalences must be a hash or nil and contain mappings to valid alphabet characters."
|
|
95
124
|
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rbs_inline: enabled
|
|
4
|
+
|
|
5
|
+
module EncodedId
|
|
6
|
+
class Blocklist
|
|
7
|
+
include Enumerable #[String]
|
|
8
|
+
|
|
9
|
+
# @rbs @words: Set[String]
|
|
10
|
+
|
|
11
|
+
# Class instance variables for memoization
|
|
12
|
+
# @rbs self.@empty: Blocklist
|
|
13
|
+
# @rbs self.@minimal: Blocklist
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
# @rbs () -> Blocklist
|
|
17
|
+
def sqids_blocklist
|
|
18
|
+
if defined?(::Sqids::DEFAULT_BLOCKLIST)
|
|
19
|
+
new(::Sqids::DEFAULT_BLOCKLIST)
|
|
20
|
+
else
|
|
21
|
+
empty
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# @rbs () -> Blocklist
|
|
26
|
+
def empty
|
|
27
|
+
@empty ||= new([])
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @rbs () -> Blocklist
|
|
31
|
+
def minimal
|
|
32
|
+
@minimal ||= new([
|
|
33
|
+
"ass", "cum", "fag", "fap", "fck", "fuk", "jiz", "pis", "poo", "sex",
|
|
34
|
+
"tit", "xxx", "anal", "anus", "ball", "blow", "butt", "clit", "cock",
|
|
35
|
+
"coon", "cunt", "dick", "dyke", "fart", "fuck", "jerk", "jizz", "jugs",
|
|
36
|
+
"kike", "kunt", "muff", "nigg", "nigr", "piss", "poon", "poop", "porn",
|
|
37
|
+
"pube", "pusy", "quim", "rape", "scat", "scum", "shit", "slut", "suck",
|
|
38
|
+
"turd", "twat", "vag", "wank", "whor"
|
|
39
|
+
])
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
attr_reader :words #: Set[String]
|
|
44
|
+
|
|
45
|
+
# @rbs (?(Array[String] | Set[String]) words) -> void
|
|
46
|
+
def initialize(words = [])
|
|
47
|
+
@words = if words.is_a?(Array) || words.is_a?(Set)
|
|
48
|
+
Set.new(words.map(&:to_s).map(&:downcase))
|
|
49
|
+
else
|
|
50
|
+
Set.new
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# @rbs () { (String) -> void } -> void
|
|
55
|
+
def each(&block)
|
|
56
|
+
@words.each(&block)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @rbs (String word) -> bool
|
|
60
|
+
def include?(word)
|
|
61
|
+
@words.include?(word.to_s.downcase)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# @rbs (String string) -> (String | false)
|
|
65
|
+
def blocks?(string)
|
|
66
|
+
return false if empty?
|
|
67
|
+
|
|
68
|
+
downcased_string = string.to_s.downcase
|
|
69
|
+
@words.each do |word|
|
|
70
|
+
return word if downcased_string.include?(word)
|
|
71
|
+
end
|
|
72
|
+
false
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# @rbs () -> Integer
|
|
76
|
+
def size
|
|
77
|
+
@words.size
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# @rbs () -> bool
|
|
81
|
+
def empty?
|
|
82
|
+
@words.empty?
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# @rbs (Blocklist other_blocklist) -> Blocklist
|
|
86
|
+
def merge(other_blocklist)
|
|
87
|
+
self.class.new(to_a + other_blocklist.to_a)
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# rbs_inline: enabled
|
|
4
|
+
|
|
5
|
+
module EncodedId
|
|
6
|
+
module Encoders
|
|
7
|
+
class Base
|
|
8
|
+
# @rbs @min_hash_length: Integer
|
|
9
|
+
# @rbs @alphabet: Alphabet
|
|
10
|
+
# @rbs @salt: String
|
|
11
|
+
# @rbs @blocklist: Blocklist
|
|
12
|
+
|
|
13
|
+
# @rbs (String salt, ?Integer min_hash_length, ?Alphabet alphabet, ?Blocklist blocklist) -> void
|
|
14
|
+
def initialize(salt, min_hash_length = 0, alphabet = Alphabet.alphanum, blocklist = Blocklist.empty)
|
|
15
|
+
@min_hash_length = min_hash_length
|
|
16
|
+
@alphabet = alphabet
|
|
17
|
+
@salt = salt
|
|
18
|
+
@blocklist = blocklist
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attr_reader :min_hash_length #: Integer
|
|
22
|
+
attr_reader :alphabet #: Alphabet
|
|
23
|
+
attr_reader :salt #: String
|
|
24
|
+
attr_reader :blocklist #: Blocklist
|
|
25
|
+
|
|
26
|
+
# Encode array of numbers into a string
|
|
27
|
+
# @rbs (Array[Integer] numbers) -> String
|
|
28
|
+
def encode(numbers)
|
|
29
|
+
raise NotImplementedError, "#{self.class} must implement #encode"
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Encode hexadecimal string(s) into a string
|
|
33
|
+
# @rbs (String str) -> String
|
|
34
|
+
def encode_hex(str)
|
|
35
|
+
return "" unless hex_string?(str)
|
|
36
|
+
|
|
37
|
+
numbers = str.scan(/[\w\W]{1,12}/).map do |num|
|
|
38
|
+
"1#{num}".to_i(16)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
encode(numbers)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Decode a string back into an array of numbers
|
|
45
|
+
# @rbs (String hash) -> Array[Integer]
|
|
46
|
+
def decode(hash)
|
|
47
|
+
raise NotImplementedError, "#{self.class} must implement #decode"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Decode a string back into an array of hexadecimal strings
|
|
51
|
+
# @rbs (String hash) -> String
|
|
52
|
+
def decode_hex(hash)
|
|
53
|
+
numbers = decode(hash)
|
|
54
|
+
return "" if numbers.empty?
|
|
55
|
+
|
|
56
|
+
ret = numbers.map do |n|
|
|
57
|
+
n.to_s(16)[1..]
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
ret.join.upcase
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
# @rbs (String string) -> MatchData?
|
|
66
|
+
def hex_string?(string)
|
|
67
|
+
string.to_s.match(/\A[0-9a-fA-F]+\Z/)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|