rucaptcha 2.5.5 → 3.0.0.beta1

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
@@ -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: []