rucaptcha 2.6.1 → 3.0.0.beta1

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.
@@ -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.beta1"
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.beta1
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,13 @@ extensions:
45
45
  - ext/rucaptcha/extconf.rb
46
46
  extra_rdoc_files: []
47
47
  files:
48
- - CHANGELOG.md
49
48
  - README.md
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
- - config/routes.rb
56
- - ext/rucaptcha/colors.h
49
+ - Rakefile
50
+ - ext/rucaptcha/Cargo.lock
51
+ - ext/rucaptcha/Cargo.toml
57
52
  - ext/rucaptcha/extconf.rb
58
- - ext/rucaptcha/font.h
59
- - ext/rucaptcha/rucaptcha.c
53
+ - ext/rucaptcha/src/captcha.rs
54
+ - ext/rucaptcha/src/lib.rs
60
55
  - lib/rucaptcha.rb
61
56
  - lib/rucaptcha/cache.rb
62
57
  - lib/rucaptcha/configuration.rb
@@ -77,16 +72,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
77
72
  requirements:
78
73
  - - ">="
79
74
  - !ruby/object:Gem::Version
80
- version: 2.0.0
75
+ version: 2.7.0
81
76
  required_rubygems_version: !ruby/object:Gem::Requirement
82
77
  requirements:
83
- - - ">="
78
+ - - ">"
84
79
  - !ruby/object:Gem::Version
85
- version: '0'
80
+ version: 1.3.1
86
81
  requirements: []
87
- rubygems_version: 3.2.3
82
+ rubygems_version: 3.4.0.dev
88
83
  signing_key:
89
84
  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.
85
+ summary: Captcha Gem for Rails, which generates captcha image by Rust.
92
86
  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,13 +0,0 @@
1
- module RuCaptcha
2
- class CaptchaController < ActionController::Base
3
- def index
4
- return head :ok if request.head?
5
-
6
- headers["Cache-Control"] = "no-cache, no-store, max-age=0, must-revalidate"
7
- headers["Pragma"] = "no-cache"
8
- data = generate_rucaptcha
9
- opts = {disposition: "inline", type: "image/gif"}
10
- send_data data, opts
11
- end
12
- end
13
- end
@@ -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: "驗證碼不正確(如無法識別,可以點擊刷新驗證碼)"
data/config/routes.rb DELETED
@@ -1,3 +0,0 @@
1
- RuCaptcha::Engine.routes.draw do
2
- root to: "captcha#index"
3
- end