rucaptcha 2.5.5 → 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
@@ -6,19 +6,24 @@ module RuCaptcha
6
6
  helper_method :verify_rucaptcha?
7
7
  end
8
8
 
9
+ def rucaptcha_session_id
10
+ cookies[:_rucaptcha_session_id]
11
+ end
12
+
9
13
  # session key of rucaptcha
10
14
  def rucaptcha_sesion_key_key
11
- session_id = session.respond_to?(:id) ? session.id : session[:session_id]
12
- warning_when_session_invalid if session_id.blank?
15
+ warning_when_session_invalid if rucaptcha_session_id.blank?
13
16
 
14
17
  # With https://github.com/rack/rack/commit/7fecaee81f59926b6e1913511c90650e76673b38
15
18
  # to protected session_id into secret
16
- session_id_digest = Digest::SHA256.hexdigest(session_id.inspect)
19
+ session_id_digest = Digest::SHA256.hexdigest(rucaptcha_session_id.inspect)
17
20
  ["rucaptcha-session", session_id_digest].join(":")
18
21
  end
19
22
 
20
23
  # Generate a new Captcha
21
24
  def generate_rucaptcha
25
+ generate_rucaptcha_session_id
26
+
22
27
  res = RuCaptcha.generate
23
28
  session_val = {
24
29
  code: res[0],
@@ -67,6 +72,15 @@ module RuCaptcha
67
72
 
68
73
  private
69
74
 
75
+ def generate_rucaptcha_session_id
76
+ return if rucaptcha_session_id.present?
77
+
78
+ cookies[:_rucaptcha_session_id] = {
79
+ value: SecureRandom.hex(16),
80
+ expires: 1.day
81
+ }
82
+ end
83
+
70
84
  def add_rucaptcha_validation_error
71
85
  if defined?(resource) && resource && resource.respond_to?(:errors)
72
86
  resource.errors.add(:base, t("rucaptcha.invalid"))
@@ -1,3 +1,3 @@
1
1
  module RuCaptcha
2
- VERSION = "2.5.5"
2
+ VERSION = "3.0.0.beta1"
3
3
  end
@@ -1,19 +1,20 @@
1
1
  module RuCaptcha
2
2
  module ViewHelpers
3
3
  def rucaptcha_input_tag(opts = {})
4
- opts[:name] = "_rucaptcha"
5
- opts[:type] = "text"
6
- opts[:autocorrect] = "off"
4
+ opts[:name] = "_rucaptcha"
5
+ opts[:type] = "text"
6
+ opts[:autocorrect] = "off"
7
7
  opts[:autocapitalize] = "off"
8
- opts[:pattern] = "[a-zA-Z]*"
9
- opts[:autocomplete] = "off"
10
- opts[:maxlength] = RuCaptcha.config.length
8
+ opts[:pattern] = "[a-zA-Z0-9]*"
9
+ opts[:autocomplete] = "off"
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
16
  opts[:class] = opts[:class] || "rucaptcha-image"
16
- opts[:src] = ru_captcha.root_path
17
+ opts[:src] = @rucaptcha_image_tag__image_path_in_this_request
17
18
  opts[:onclick] = "this.src = '#{ru_captcha.root_path}?t=' + Date.now();"
18
19
  tag(:img, opts)
19
20
  end
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,11 +24,9 @@ module RuCaptcha
16
24
  return @config if defined?(@config)
17
25
 
18
26
  @config = Configuration.new
19
- @config.style = :colorful
20
- @config.length = 5
21
- @config.strikethrough = true
22
- @config.outline = false
23
- @config.expires_in = 2.minutes
27
+ @config.length = 5
28
+ @config.difficulty = 3
29
+ @config.expires_in = 2.minutes
24
30
  @config.skip_cache_store_check = false
25
31
 
26
32
  @config.cache_store = if Rails.application
@@ -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.5.5
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: 2021-01-27 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: []