rucaptcha 3.0.0.beta1-x86_64-darwin

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"
@@ -0,0 +1,4 @@
1
+ require "mkmf"
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
+ }
Binary file
Binary file
Binary file
@@ -0,0 +1,12 @@
1
+ require "fileutils"
2
+
3
+ module RuCaptcha
4
+ class << self
5
+ def cache
6
+ return @cache if defined? @cache
7
+
8
+ @cache = ActiveSupport::Cache.lookup_store(RuCaptcha.config.cache_store)
9
+ @cache
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ module RuCaptcha
2
+ class Configuration
3
+ # Store Captcha code where, this config more like Rails config.cache_store
4
+ # default: Rails application config.cache_store
5
+ attr_accessor :cache_store
6
+ # rucaptcha expire time, default 2 minutes
7
+ attr_accessor :expires_in
8
+ # Chars length: default 5, allows: [3..7]
9
+ attr_accessor :length
10
+ # Hard mode, default: 5, allows: [1..10]
11
+ attr_accessor :difficulty
12
+ # skip_cache_store_check, default: false
13
+ attr_accessor :skip_cache_store_check
14
+ end
15
+ end
@@ -0,0 +1,100 @@
1
+ module RuCaptcha
2
+ module ControllerHelpers
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ helper_method :verify_rucaptcha?
7
+ end
8
+
9
+ def rucaptcha_session_id
10
+ cookies[:_rucaptcha_session_id]
11
+ end
12
+
13
+ # session key of rucaptcha
14
+ def rucaptcha_sesion_key_key
15
+ warning_when_session_invalid if rucaptcha_session_id.blank?
16
+
17
+ # With https://github.com/rack/rack/commit/7fecaee81f59926b6e1913511c90650e76673b38
18
+ # to protected session_id into secret
19
+ session_id_digest = Digest::SHA256.hexdigest(rucaptcha_session_id.inspect)
20
+ ["rucaptcha-session", session_id_digest].join(":")
21
+ end
22
+
23
+ # Generate a new Captcha
24
+ def generate_rucaptcha
25
+ generate_rucaptcha_session_id
26
+
27
+ res = RuCaptcha.generate
28
+ session_val = {
29
+ code: res[0],
30
+ time: Time.now.to_i
31
+ }
32
+ RuCaptcha.cache.write(rucaptcha_sesion_key_key, session_val, expires_in: RuCaptcha.config.expires_in)
33
+ res[1]
34
+ end
35
+
36
+ # Verify captcha code
37
+ #
38
+ # params:
39
+ # resource - [optional] a ActiveModel object, if given will add validation error message to object.
40
+ # :keep_session - if true, RuCaptcha will not delete the captcha code session.
41
+ # :captcha - if given, the value of it will be used to verify the captcha,
42
+ # if do not give or blank, the value of params[:_rucaptcha] will be used to verify the captcha
43
+ #
44
+ # exmaples:
45
+ #
46
+ # verify_rucaptcha?
47
+ # verify_rucaptcha?(user, keep_session: true)
48
+ # verify_rucaptcha?(nil, keep_session: true)
49
+ # verify_rucaptcha?(nil, captcha: params[:user][:captcha])
50
+ #
51
+ def verify_rucaptcha?(_resource = nil, opts = {})
52
+ opts ||= {}
53
+
54
+ store_info = RuCaptcha.cache.read(rucaptcha_sesion_key_key)
55
+ # make sure move used key
56
+ RuCaptcha.cache.delete(rucaptcha_sesion_key_key) unless opts[:keep_session]
57
+
58
+ # Make sure session exist
59
+ return add_rucaptcha_validation_error if store_info.blank?
60
+
61
+ # Make sure not expire
62
+ return add_rucaptcha_validation_error if (Time.now.to_i - store_info[:time]) > RuCaptcha.config.expires_in
63
+
64
+ # Make sure parama have captcha
65
+ captcha = (opts[:captcha] || params[:_rucaptcha] || "").downcase.strip
66
+ return add_rucaptcha_validation_error if captcha.blank?
67
+
68
+ return add_rucaptcha_validation_error if captcha != store_info[:code]
69
+
70
+ true
71
+ end
72
+
73
+ private
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
+
84
+ def add_rucaptcha_validation_error
85
+ if defined?(resource) && resource && resource.respond_to?(:errors)
86
+ resource.errors.add(:base, t("rucaptcha.invalid"))
87
+ end
88
+ false
89
+ end
90
+
91
+ def warning_when_session_invalid
92
+ return unless Rails.env.development?
93
+
94
+ Rails.logger.warn "
95
+ WARNING! The session.id is blank, RuCaptcha can't work properly, please keep session available.
96
+ More details about this: https://github.com/huacnlee/rucaptcha/pull/66
97
+ "
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,15 @@
1
+ module RuCaptcha
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace RuCaptcha
4
+
5
+ initializer "rucaptcha.init" do |app|
6
+ # https://github.com/rails/rails/blob/3-2-stable/actionpack/lib/action_dispatch/routing/route_set.rb#L268
7
+ # `app.routes.prepend` start from Rails 3.2 - 5.0
8
+ app.routes.prepend do
9
+ mount RuCaptcha::Engine => "/rucaptcha"
10
+ end
11
+
12
+ RuCaptcha.check_cache_store! unless RuCaptcha.config.skip_cache_store_check
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,5 @@
1
+ module RuCaptcha
2
+ module Errors
3
+ class Configuration < StandardError; end
4
+ end
5
+ end
@@ -0,0 +1,3 @@
1
+ module RuCaptcha
2
+ VERSION = "3.0.0.beta1"
3
+ end
@@ -0,0 +1,22 @@
1
+ module RuCaptcha
2
+ module ViewHelpers
3
+ def rucaptcha_input_tag(opts = {})
4
+ opts[:name] = "_rucaptcha"
5
+ opts[:type] = "text"
6
+ opts[:autocorrect] = "off"
7
+ opts[:autocapitalize] = "off"
8
+ opts[:pattern] = "[a-zA-Z0-9]*"
9
+ opts[:autocomplete] = "off"
10
+ opts[:maxlength] = RuCaptcha.config.length
11
+ tag(:input, opts)
12
+ end
13
+
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')}"
16
+ opts[:class] = opts[:class] || "rucaptcha-image"
17
+ opts[:src] = @rucaptcha_image_tag__image_path_in_this_request
18
+ opts[:onclick] = "this.src = '#{ru_captcha.root_path}?t=' + Date.now();"
19
+ tag(:img, opts)
20
+ end
21
+ end
22
+ end
data/lib/rucaptcha.rb ADDED
@@ -0,0 +1,83 @@
1
+ require "rails"
2
+ require "action_controller"
3
+ require "active_support/all"
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
+
13
+ require "rucaptcha/version"
14
+ require "rucaptcha/configuration"
15
+ require "rucaptcha/controller_helpers"
16
+ require "rucaptcha/view_helpers"
17
+ require "rucaptcha/cache"
18
+ require "rucaptcha/engine"
19
+ require "rucaptcha/errors/configuration"
20
+
21
+ module RuCaptcha
22
+ class << self
23
+ def config
24
+ return @config if defined?(@config)
25
+
26
+ @config = Configuration.new
27
+ @config.length = 5
28
+ @config.difficulty = 3
29
+ @config.expires_in = 2.minutes
30
+ @config.skip_cache_store_check = false
31
+
32
+ @config.cache_store = if Rails.application
33
+ Rails.application.config.cache_store
34
+ else
35
+ :mem_cache_store
36
+ end
37
+ @config.cache_store
38
+ @config
39
+ end
40
+
41
+ def configure(&block)
42
+ config.instance_exec(&block)
43
+ end
44
+
45
+ def generate
46
+ length = config.length
47
+
48
+ raise RuCaptcha::Errors::Configuration, "length config error, value must in 3..7" unless length.in?(3..7)
49
+
50
+ result = RuCaptchaCore.create(length, config.difficulty || 5)
51
+ [result[0], result[1].pack("c*")]
52
+ end
53
+
54
+ def check_cache_store!
55
+ cache_store = RuCaptcha.config.cache_store
56
+ store_name = cache_store.is_a?(Array) ? cache_store.first : cache_store
57
+ if %i[memory_store null_store file_store].include?(store_name)
58
+ RuCaptcha.config.cache_store = [:file_store, Rails.root.join("tmp/cache/rucaptcha/session")]
59
+
60
+ puts "
61
+
62
+ RuCaptcha's cache_store requirements are stored across processes and machines,
63
+ such as :mem_cache_store, :redis_store, or other distributed storage.
64
+ But your current set is #{cache_store}, it has changed to :file_store for working.
65
+ NOTE: :file_store is still not a good way, it only works with single server case.
66
+
67
+ Please make config file `config/initializers/rucaptcha.rb` to setup `cache_store`.
68
+ More infomation please read GitHub RuCaptcha README file.
69
+ https://github.com/huacnlee/rucaptcha
70
+
71
+ "
72
+ end
73
+ end
74
+ end
75
+ end
76
+
77
+ ActiveSupport.on_load(:action_controller) do
78
+ ActionController::Base.include RuCaptcha::ControllerHelpers
79
+ end
80
+
81
+ ActiveSupport.on_load(:action_view) do
82
+ include RuCaptcha::ViewHelpers
83
+ end
metadata ADDED
@@ -0,0 +1,91 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rucaptcha
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.0.0.beta1
5
+ platform: x86_64-darwin
6
+ authors:
7
+ - Jason Lee
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2022-10-13 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: railties
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rb_sys
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: 0.9.18
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: 0.9.18
41
+ description:
42
+ email: huacnlee@gmail.com
43
+ executables: []
44
+ extensions: []
45
+ extra_rdoc_files: []
46
+ files:
47
+ - README.md
48
+ - Rakefile
49
+ - ext/rucaptcha/Cargo.lock
50
+ - ext/rucaptcha/Cargo.toml
51
+ - ext/rucaptcha/extconf.rb
52
+ - ext/rucaptcha/src/captcha.rs
53
+ - ext/rucaptcha/src/lib.rs
54
+ - lib/rucaptcha.rb
55
+ - lib/rucaptcha/2.7/rucaptcha.bundle
56
+ - lib/rucaptcha/3.0/rucaptcha.bundle
57
+ - lib/rucaptcha/3.1/rucaptcha.bundle
58
+ - lib/rucaptcha/cache.rb
59
+ - lib/rucaptcha/configuration.rb
60
+ - lib/rucaptcha/controller_helpers.rb
61
+ - lib/rucaptcha/engine.rb
62
+ - lib/rucaptcha/errors/configuration.rb
63
+ - lib/rucaptcha/version.rb
64
+ - lib/rucaptcha/view_helpers.rb
65
+ homepage: https://github.com/huacnlee/rucaptcha
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: '2.7'
78
+ - - "<"
79
+ - !ruby/object:Gem::Version
80
+ version: 3.2.dev
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">"
84
+ - !ruby/object:Gem::Version
85
+ version: 1.3.1
86
+ requirements: []
87
+ rubygems_version: 3.4.0.dev
88
+ signing_key:
89
+ specification_version: 4
90
+ summary: Captcha Gem for Rails, which generates captcha image by Rust.
91
+ test_files: []