rucaptcha 2.6.1 → 3.0.0.beta2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ [package]
2
+ edition = "2021"
3
+ name = "rucaptcha"
4
+ version = "3.0.0"
5
+
6
+ [lib]
7
+ crate-type = ["cdylib"]
8
+
9
+ [dependencies]
10
+ image = "0.24.4"
11
+ imageproc = "0.23.0"
12
+ magnus = "0.3"
13
+ rand = "0.8.5"
14
+ rusttype = "0.9.2"
@@ -1,2 +1,4 @@
1
1
  require "mkmf"
2
- create_makefile("rucaptcha/rucaptcha")
2
+ require "rb_sys/mkmf"
3
+
4
+ create_rust_makefile("rucaptcha/rucaptcha")
@@ -0,0 +1,250 @@
1
+ use image::{ImageBuffer, Rgb};
2
+ use imageproc::drawing::{draw_cubic_bezier_curve_mut, draw_hollow_ellipse_mut, draw_text_mut};
3
+ use imageproc::noise::{gaussian_noise_mut, salt_and_pepper_noise_mut};
4
+ use rand::{thread_rng, Rng};
5
+ use rusttype::{Font, Scale};
6
+ use std::io::Cursor;
7
+
8
+ static BASIC_CHAR: [char; 54] = [
9
+ '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M',
10
+ 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
11
+ 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
12
+ ];
13
+
14
+ static FONT_BYTES1: &[u8; 145008] = include_bytes!("../fonts/FuzzyBubbles-Regular.ttf");
15
+ static FONT_BYTES2: &[u8; 37792] = include_bytes!("../fonts/Handlee-Regular.ttf");
16
+
17
+ static COLORS: [(u8, u8, u8); 10] = [
18
+ (204, 11, 143),
19
+ (124, 10, 190),
20
+ (87, 0, 200),
21
+ (61, 86, 168),
22
+ (63, 166, 126),
23
+ (69, 187, 48),
24
+ (105, 208, 3),
25
+ (160, 208, 3),
26
+ (216, 219, 2),
27
+ (50, 50, 50),
28
+ ];
29
+
30
+ static SCALE_SM: u32 = 32;
31
+ static SCALE_MD: u32 = 45;
32
+ static SCALE_LG: u32 = 55;
33
+
34
+ fn rand_num(len: usize) -> usize {
35
+ let mut rng = thread_rng();
36
+ rng.gen_range(0..=len)
37
+ }
38
+
39
+ fn get_captcha(len: usize) -> Vec<String> {
40
+ let mut res = vec![];
41
+ for _ in 0..len {
42
+ let rnd = rand_num(53);
43
+ res.push(BASIC_CHAR[rnd].to_string())
44
+ }
45
+ res
46
+ }
47
+
48
+ #[allow(unused)]
49
+ fn get_color() -> Rgb<u8> {
50
+ let rnd = rand_num(COLORS.len());
51
+ let c = COLORS[rnd];
52
+ Rgb([c.0, c.1, c.2])
53
+ }
54
+
55
+ fn get_colors(num: usize) -> Vec<Rgb<u8>> {
56
+ let rnd = rand_num(COLORS.len());
57
+ let mut out = vec![];
58
+ for i in 0..num {
59
+ let c = COLORS[(rnd + i) % COLORS.len()];
60
+ out.push(Rgb([c.0, c.1, c.2]))
61
+ }
62
+
63
+ out
64
+ }
65
+
66
+ fn get_next(min: f32, max: u32) -> f32 {
67
+ min + rand_num(max as usize - min as usize) as f32
68
+ }
69
+
70
+ fn get_font() -> Font<'static> {
71
+ match rand_num(2) {
72
+ 0 => Font::try_from_bytes(FONT_BYTES1).unwrap(),
73
+ 1 => Font::try_from_bytes(FONT_BYTES2).unwrap(),
74
+ _ => Font::try_from_bytes(FONT_BYTES1).unwrap(),
75
+ }
76
+ }
77
+
78
+ fn get_image(width: usize, height: usize) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
79
+ ImageBuffer::from_fn(width as u32, height as u32, |_, _| {
80
+ image::Rgb([255, 255, 255])
81
+ })
82
+ }
83
+
84
+ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>) {
85
+ let c = (image.width() - 20) / res.len() as u32;
86
+ let y = image.height() / 3 - 15;
87
+
88
+ let h = image.height() as f32;
89
+
90
+ let scale = match res.len() {
91
+ 1..=3 => SCALE_LG,
92
+ 4..=5 => SCALE_MD,
93
+ _ => SCALE_SM,
94
+ } as f32;
95
+
96
+ let colors = get_colors(res.len());
97
+
98
+ let xscale = scale - rand_num((scale * 0.2) as usize) as f32;
99
+ let yscale = h as f32 - rand_num((h * 0.2) as usize) as f32;
100
+
101
+ for (i, _) in res.iter().enumerate() {
102
+ let text = &res[i];
103
+
104
+ let color = colors[i];
105
+ let font = get_font();
106
+ let line_color = colors[rand_num(colors.len() - 1)];
107
+
108
+ for j in 0..(rand_num(2) + 1) as i32 {
109
+ let offset = j * (rand_num(3) as i32 + 1);
110
+ draw_text_mut(
111
+ image,
112
+ color,
113
+ 10 + offset + (i as u32 * c) as i32,
114
+ y as i32 as i32,
115
+ Scale {
116
+ x: xscale + offset as f32,
117
+ y: yscale as f32,
118
+ },
119
+ &font,
120
+ text,
121
+ );
122
+ }
123
+
124
+ draw_interference_line(1, image, line_color);
125
+ draw_interference_ellipse(1, image, line_color);
126
+ }
127
+ }
128
+
129
+ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, color: Rgb<u8>) {
130
+ for _ in 0..num {
131
+ let width = image.width();
132
+ let height = image.height();
133
+ let x1: f32 = 5.0;
134
+ let y1 = get_next(x1, height / 2);
135
+
136
+ let x2 = (width - 5) as f32;
137
+ let y2 = get_next(5.0, height - 5);
138
+
139
+ let ctrl_x = get_next((width / 6) as f32, width / 4 * 3);
140
+ let ctrl_y = get_next(x1, height - 5);
141
+
142
+ let ctrl_x2 = get_next((width / 4) as f32, width / 4 * 3);
143
+ let ctrl_y2 = get_next(x1, height - 5);
144
+ // Randomly draw bezier curves
145
+ draw_cubic_bezier_curve_mut(
146
+ image,
147
+ (x1, y1),
148
+ (x2, y2),
149
+ (ctrl_x, ctrl_y),
150
+ (ctrl_x2, ctrl_y2),
151
+ color,
152
+ );
153
+ }
154
+ }
155
+
156
+ fn draw_interference_ellipse(
157
+ num: usize,
158
+ image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
159
+ color: Rgb<u8>,
160
+ ) {
161
+ for _ in 0..num {
162
+ let w = (10 + rand_num(10)) as i32;
163
+ let x = rand_num((image.width() - 25) as usize) as i32;
164
+ let y = rand_num((image.height() - 15) as usize) as i32;
165
+ draw_hollow_ellipse_mut(image, (x, y), w, w, color);
166
+ }
167
+ }
168
+
169
+ pub struct Captcha {
170
+ pub text: String,
171
+ pub image: Vec<u8>,
172
+ }
173
+
174
+ pub struct CaptchaBuilder {
175
+ length: usize,
176
+ width: usize,
177
+ height: usize,
178
+ complexity: usize,
179
+ }
180
+
181
+ impl CaptchaBuilder {
182
+ pub fn new() -> Self {
183
+ CaptchaBuilder {
184
+ length: 4,
185
+ width: 240,
186
+ height: 64,
187
+ complexity: 5,
188
+ }
189
+ }
190
+
191
+ pub fn length(mut self, length: usize) -> Self {
192
+ self.length = length;
193
+ self
194
+ }
195
+
196
+ // pub fn width(mut self, width: usize) -> Self {
197
+ // self.width = width;
198
+ // self
199
+ // }
200
+
201
+ // pub fn height(mut self, height: usize) -> Self {
202
+ // self.height = height;
203
+ // self
204
+ // }
205
+
206
+ pub fn complexity(mut self, complexity: usize) -> Self {
207
+ let mut complexity = complexity;
208
+ if complexity > 10 {
209
+ complexity = 10;
210
+ }
211
+ if complexity < 1 {
212
+ complexity = 1;
213
+ }
214
+ self.complexity = complexity;
215
+ self
216
+ }
217
+
218
+ pub fn build(self) -> Captcha {
219
+ // Generate an array of captcha characters
220
+ let res = get_captcha(self.length);
221
+
222
+ let text = res.join("");
223
+
224
+ // Create a white background image
225
+ let mut image = get_image(self.width, self.height);
226
+
227
+ // Loop to write the verification code string into the background image
228
+ cyclic_write_character(&res, &mut image);
229
+
230
+ gaussian_noise_mut(
231
+ &mut image,
232
+ (self.complexity - 1) as f64,
233
+ ((10 * self.complexity) - 10) as f64,
234
+ ((5 * self.complexity) - 5) as u64,
235
+ );
236
+
237
+ salt_and_pepper_noise_mut(
238
+ &mut image,
239
+ ((0.001 * self.complexity as f64) - 0.001) as f64,
240
+ (0.8 * self.complexity as f64) as u64,
241
+ );
242
+
243
+ let mut bytes: Vec<u8> = Vec::new();
244
+ image
245
+ .write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)
246
+ .unwrap();
247
+
248
+ Captcha { text, image: bytes }
249
+ }
250
+ }
@@ -0,0 +1,18 @@
1
+ use magnus::{define_class, function, Error, Object};
2
+
3
+ mod captcha;
4
+
5
+ pub fn create(len: usize, difficulty: usize) -> (String, Vec<u8>) {
6
+ let c = captcha::CaptchaBuilder::new();
7
+ let out = c.complexity(difficulty).length(len).build();
8
+
9
+ (out.text, out.image)
10
+ }
11
+
12
+ #[magnus::init]
13
+ fn init() -> Result<(), Error> {
14
+ let class = define_class("RuCaptchaCore", Default::default())?;
15
+ class.define_singleton_method("create", function!(create, 2))?;
16
+
17
+ Ok(())
18
+ }
@@ -5,14 +5,10 @@ module RuCaptcha
5
5
  attr_accessor :cache_store
6
6
  # rucaptcha expire time, default 2 minutes
7
7
  attr_accessor :expires_in
8
- # Color style, default: :colorful, allows: [:colorful, :black_white]
9
- attr_accessor :style
10
8
  # Chars length: default 5, allows: [3..7]
11
9
  attr_accessor :length
12
- # strikethrough, default: true
13
- attr_accessor :strikethrough
14
- # outline style for hard mode, default: false
15
- attr_accessor :outline
10
+ # Hard mode, default: 5, allows: [1..10]
11
+ attr_accessor :difficulty
16
12
  # skip_cache_store_check, default: false
17
13
  attr_accessor :skip_cache_store_check
18
14
  end
@@ -1,3 +1,3 @@
1
1
  module RuCaptcha
2
- VERSION = "2.6.1"
2
+ VERSION = "3.0.0.beta2"
3
3
  end
@@ -5,14 +5,14 @@ module RuCaptcha
5
5
  opts[:type] = "text"
6
6
  opts[:autocorrect] = "off"
7
7
  opts[:autocapitalize] = "off"
8
- opts[:pattern] = "[a-zA-Z]*"
8
+ opts[:pattern] = "[a-zA-Z0-9]*"
9
9
  opts[:autocomplete] = "off"
10
10
  opts[:maxlength] = RuCaptcha.config.length
11
11
  tag(:input, opts)
12
12
  end
13
13
 
14
14
  def rucaptcha_image_tag(opts = {})
15
- @rucaptcha_image_tag__image_path_in_this_request ||= "#{ru_captcha.root_path}?t=#{Time.now.strftime("%s%L")}"
15
+ @rucaptcha_image_tag__image_path_in_this_request ||= "#{ru_captcha.root_path}?t=#{Time.now.strftime('%s%L')}"
16
16
  opts[:class] = opts[:class] || "rucaptcha-image"
17
17
  opts[:src] = @rucaptcha_image_tag__image_path_in_this_request
18
18
  opts[:onclick] = "this.src = '#{ru_captcha.root_path}?t=' + Date.now();"
data/lib/rucaptcha.rb CHANGED
@@ -1,7 +1,15 @@
1
1
  require "rails"
2
2
  require "action_controller"
3
3
  require "active_support/all"
4
- require "rucaptcha/rucaptcha"
4
+
5
+ begin
6
+ # load the precompiled extension file
7
+ ruby_version = /(\d+\.\d+)/.match(::RUBY_VERSION)
8
+ require_relative "rucaptcha/#{ruby_version}/rucaptcha"
9
+ rescue LoadError
10
+ require "rucaptcha/rucaptcha"
11
+ end
12
+
5
13
  require "rucaptcha/version"
6
14
  require "rucaptcha/configuration"
7
15
  require "rucaptcha/controller_helpers"
@@ -16,18 +24,16 @@ module RuCaptcha
16
24
  return @config if defined?(@config)
17
25
 
18
26
  @config = Configuration.new
19
- @config.style = :colorful
20
27
  @config.length = 5
21
- @config.strikethrough = true
22
- @config.outline = false
28
+ @config.difficulty = 3
23
29
  @config.expires_in = 2.minutes
24
30
  @config.skip_cache_store_check = false
25
31
 
26
32
  @config.cache_store = if Rails.application
27
- Rails.application.config.cache_store
28
- else
29
- :mem_cache_store
30
- end
33
+ Rails.application.config.cache_store
34
+ else
35
+ :mem_cache_store
36
+ end
31
37
  @config.cache_store
32
38
  @config
33
39
  end
@@ -37,14 +43,12 @@ module RuCaptcha
37
43
  end
38
44
 
39
45
  def generate
40
- style = config.style == :colorful ? 1 : 0
41
46
  length = config.length
42
47
 
43
48
  raise RuCaptcha::Errors::Configuration, "length config error, value must in 3..7" unless length.in?(3..7)
44
49
 
45
- strikethrough = config.strikethrough ? 1 : 0
46
- outline = config.outline ? 1 : 0
47
- create(style, length, strikethrough, outline)
50
+ result = RuCaptchaCore.create(length, config.difficulty || 5)
51
+ [result[0], result[1].pack("c*")]
48
52
  end
49
53
 
50
54
  def check_cache_store!
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rucaptcha
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.6.1
4
+ version: 3.0.0.beta2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jason Lee
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-02-09 00:00:00.000000000 Z
11
+ date: 2022-10-13 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: railties
@@ -25,19 +25,19 @@ dependencies:
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
27
  - !ruby/object:Gem::Dependency
28
- name: rake-compiler
28
+ name: rb_sys
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '1'
34
- type: :development
33
+ version: 0.9.18
34
+ type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '1'
40
+ version: 0.9.18
41
41
  description:
42
42
  email: huacnlee@gmail.com
43
43
  executables: []
@@ -45,18 +45,15 @@ extensions:
45
45
  - ext/rucaptcha/extconf.rb
46
46
  extra_rdoc_files: []
47
47
  files:
48
- - CHANGELOG.md
49
48
  - README.md
49
+ - Rakefile
50
50
  - app/controllers/ru_captcha/captcha_controller.rb
51
- - config/locales/rucaptcha.en.yml
52
- - config/locales/rucaptcha.pt-BR.yml
53
- - config/locales/rucaptcha.zh-CN.yml
54
- - config/locales/rucaptcha.zh-TW.yml
55
51
  - config/routes.rb
56
- - ext/rucaptcha/colors.h
52
+ - ext/rucaptcha/Cargo.lock
53
+ - ext/rucaptcha/Cargo.toml
57
54
  - ext/rucaptcha/extconf.rb
58
- - ext/rucaptcha/font.h
59
- - ext/rucaptcha/rucaptcha.c
55
+ - ext/rucaptcha/src/captcha.rs
56
+ - ext/rucaptcha/src/lib.rs
60
57
  - lib/rucaptcha.rb
61
58
  - lib/rucaptcha/cache.rb
62
59
  - lib/rucaptcha/configuration.rb
@@ -77,16 +74,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
74
  requirements:
78
75
  - - ">="
79
76
  - !ruby/object:Gem::Version
80
- version: 2.0.0
77
+ version: 2.7.0
81
78
  required_rubygems_version: !ruby/object:Gem::Requirement
82
79
  requirements:
83
- - - ">="
80
+ - - ">"
84
81
  - !ruby/object:Gem::Version
85
- version: '0'
82
+ version: 1.3.1
86
83
  requirements: []
87
- rubygems_version: 3.2.3
84
+ rubygems_version: 3.3.3
88
85
  signing_key:
89
86
  specification_version: 4
90
- summary: This is a Captcha gem for Rails Applications. It drawing captcha image with
91
- C code so it no dependencies.
87
+ summary: Captcha Gem for Rails, which generates captcha image by Rust.
92
88
  test_files: []
data/CHANGELOG.md DELETED
@@ -1,93 +0,0 @@
1
- New release please visit:
2
-
3
- https://github.com/huacnlee/rucaptcha/releases
4
-
5
- ## 2.5.5
6
-
7
- - Improved image for thicker interference lines.
8
- - Add more colors.
9
-
10
- ## 2.5.4
11
-
12
- - Fix: rucaptcha input maxlength attribute with config value.
13
-
14
- ## 2.5.3
15
-
16
- - Fix session invalid warning, only for development env;
17
-
18
- ## 2.5.2
19
-
20
- - Fix session.id error with upgrade Rails 6.0.2.1 or Rack 2.0.8 (#84)
21
-
22
- ## 2.5.1
23
-
24
- - Fix invalid module name error. (#78)
25
-
26
- ## 2.5.0
27
-
28
- - Support click captcha image to refresh new one by default.
29
- - Use simple tag helper generate captcha img html, for avoid asset_host (#73).
30
-
31
- ## 2.4.0
32
-
33
- - Add skip_cache_store_check configuration. (#63)
34
- - Fix for generate captcha with relative path, not url. (#58)
35
-
36
- ## 2.3.2
37
-
38
- - Change Yellow and Green colors to Pink and Deep Purple to pass WCAG 2.0's contrast test. (#70)
39
-
40
- ## 2.3.1
41
-
42
- - Fix #67 a y chars will invalid error (only in 2.3.0).
43
-
44
- ## 2.3.0
45
-
46
- - Add `config.outline` for use outline style.
47
- - Reduce colors down to 5 (red, blue, green, yellow and black).
48
-
49
- ## 2.2.0
50
-
51
- - Add option `config.length` for support change number chars. (#57)
52
- - Add option `config.strikethrough` for enable or disable strikethrough. (#57)
53
-
54
- ## 2.1.3
55
-
56
- - Windows support fixed with `send_data` method. (#45)
57
-
58
- ## 2.1.2
59
-
60
- - Do not change captcha when `HEAD /rucaptcha`.
61
-
62
- ## 2.1.1
63
-
64
- - Mount engine use `prepend` method to get high priority in config/routes.rb.
65
-
66
- ## 2.1.0
67
-
68
- - Mount Router by default, not need config now.
69
-
70
- > IMPORTANT: Wen you upgrade this version, you need remove `mount RuCaptcha::Engine` line from your `config/routes.rb`
71
-
72
- - Default use [:file_store, 'tmp/cache/rucaptcha/session'] as RuCaptcha.config.cache_store, now it can work without any configurations.
73
-
74
- > NOTE: But you still need care about `config.cache_store` to setup on a right way.
75
-
76
- ## 2.0.3
77
-
78
- - Use `ActiveSupport.on_load` to extend ActionController and ActionView.
79
-
80
- ## 2.0.1
81
-
82
- - Fix `/rucaptcha` path issue when `config.action_controller.asset_host` has setup with CDN url.
83
-
84
- ## 2.0.0
85
-
86
- _Break Changes!_
87
-
88
- WARNING!: This version have so many break changes!
89
-
90
- - Use C ext instead of ImageMagick, now it's no dependencies!
91
- - New captcha style.
92
- - Remove `len`, `font_size`, `cache_limit` config key, no support now.
93
- - Output `GIF` format.
@@ -1,3 +0,0 @@
1
- en:
2
- rucaptcha:
3
- invalid: "The captcha code is incorrect (if you can't read, you can click image to refresh it)"
@@ -1,3 +0,0 @@
1
- 'pt-BR':
2
- rucaptcha:
3
- invalid: "Código inválido!"
@@ -1,3 +0,0 @@
1
- 'zh-CN':
2
- rucaptcha:
3
- invalid: "验证码不正确(如无法识别,可以点击刷新验证码)"
@@ -1,3 +0,0 @@
1
- 'zh-TW':
2
- rucaptcha:
3
- invalid: "驗證碼不正確(如無法識別,可以點擊刷新驗證碼)"