rucaptcha 3.1.4.pre-x86_64-linux-musl

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.4"
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,257 @@
1
+ use image::{ImageBuffer, Rgb};
2
+ use imageproc::drawing::{draw_cubic_bezier_curve_mut, draw_text_mut};
3
+ use imageproc::noise::gaussian_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
+ // https://coolors.co/cc0b8f-7c0abe-5700c8-3c2ea4-3d56a8-3fa67e-45bb30-69d003-a0d003-d8db02
18
+ static COLORS: [(u8, u8, u8); 14] = [
19
+ (197, 166, 3),
20
+ (187, 87, 5),
21
+ (176, 7, 7),
22
+ (186, 9, 56),
23
+ (204, 11, 143),
24
+ (124, 10, 190),
25
+ (87, 0, 200),
26
+ (61, 86, 168),
27
+ (63, 166, 126),
28
+ (69, 187, 48),
29
+ (105, 208, 3),
30
+ (160, 208, 3),
31
+ (216, 219, 2),
32
+ (50, 50, 50),
33
+ ];
34
+
35
+ static SCALE_SM: u32 = 32;
36
+ static SCALE_MD: u32 = 45;
37
+ static SCALE_LG: u32 = 55;
38
+
39
+ fn rand_num(len: usize) -> usize {
40
+ let mut rng = thread_rng();
41
+ rng.gen_range(0..=len)
42
+ }
43
+
44
+ fn get_captcha(len: usize) -> Vec<String> {
45
+ let mut res = vec![];
46
+ for _ in 0..len {
47
+ let rnd = rand_num(53);
48
+ res.push(BASIC_CHAR[rnd].to_string())
49
+ }
50
+ res
51
+ }
52
+
53
+ #[allow(unused)]
54
+ fn get_color() -> Rgb<u8> {
55
+ let rnd = rand_num(COLORS.len() - 1);
56
+ let c = COLORS[rnd];
57
+ Rgb([c.0, c.1, c.2])
58
+ }
59
+
60
+ fn get_colors(num: usize) -> Vec<Rgb<u8>> {
61
+ let rnd = rand_num(COLORS.len());
62
+ let mut out = vec![];
63
+ for i in 0..num {
64
+ let c = COLORS[(rnd + i) % COLORS.len()];
65
+ out.push(Rgb([c.0, c.1, c.2]))
66
+ }
67
+
68
+ out
69
+ }
70
+
71
+ fn get_next(min: f32, max: u32) -> f32 {
72
+ min + rand_num(max as usize - min as usize) as f32
73
+ }
74
+
75
+ fn get_font() -> Font<'static> {
76
+ match rand_num(2) {
77
+ 0 => Font::try_from_bytes(FONT_BYTES1).unwrap(),
78
+ 1 => Font::try_from_bytes(FONT_BYTES2).unwrap(),
79
+ _ => Font::try_from_bytes(FONT_BYTES1).unwrap(),
80
+ }
81
+ }
82
+
83
+ fn get_image(width: usize, height: usize) -> ImageBuffer<Rgb<u8>, Vec<u8>> {
84
+ ImageBuffer::from_fn(width as u32, height as u32, |_, _| {
85
+ image::Rgb([255, 255, 255])
86
+ })
87
+ }
88
+
89
+ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>) {
90
+ let c = (image.width() - 20) / res.len() as u32;
91
+ let y = image.height() / 3 - 15;
92
+
93
+ let h = image.height() as f32;
94
+
95
+ let scale = match res.len() {
96
+ 1..=3 => SCALE_LG,
97
+ 4..=5 => SCALE_MD,
98
+ _ => SCALE_SM,
99
+ } as f32;
100
+
101
+ let colors = get_colors(res.len());
102
+ let line_colors = get_colors(res.len());
103
+
104
+ let xscale = scale - rand_num((scale * 0.2) as usize) as f32;
105
+ let yscale = h as f32 - rand_num((h * 0.2) as usize) as f32;
106
+
107
+ // Draw line, ellipse first as background
108
+ for (i, _) in res.iter().enumerate() {
109
+ let line_color = line_colors[i];
110
+ draw_interference_line(1, image, line_color);
111
+ draw_interference_ellipse(1, image, line_color);
112
+ }
113
+
114
+ // Draw text
115
+ for (i, _) in res.iter().enumerate() {
116
+ let text = &res[i];
117
+
118
+ let color = colors[i];
119
+ let font = get_font();
120
+
121
+ for j in 0..(rand_num(3) + 1) as i32 {
122
+ // Draw text again with offset
123
+ let offset = j * (rand_num(2) as i32);
124
+ draw_text_mut(
125
+ image,
126
+ color,
127
+ 10 + offset + (i as u32 * c) as i32,
128
+ y as i32 as i32,
129
+ Scale {
130
+ x: xscale + offset as f32,
131
+ y: yscale as f32,
132
+ },
133
+ &font,
134
+ text,
135
+ );
136
+ }
137
+ }
138
+ }
139
+
140
+ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>, color: Rgb<u8>) {
141
+ for _ in 0..num {
142
+ let width = image.width();
143
+ let height = image.height();
144
+ let x1: f32 = 5.0;
145
+ let y1 = get_next(x1, height / 2);
146
+
147
+ let x2 = (width - 5) as f32;
148
+ let y2 = get_next(5.0, height - 5);
149
+
150
+ let ctrl_x = get_next((width / 6) as f32, width / 4 * 3);
151
+ let ctrl_y = get_next(x1, height - 5);
152
+
153
+ let ctrl_x2 = get_next((width / 12) as f32, width / 12 * 3);
154
+ let ctrl_y2 = get_next(x1, height - 5);
155
+ // Randomly draw bezier curves
156
+ draw_cubic_bezier_curve_mut(
157
+ image,
158
+ (x1, y1),
159
+ (x2, y2),
160
+ (ctrl_x, ctrl_y),
161
+ (ctrl_x2, ctrl_y2),
162
+ color,
163
+ );
164
+ }
165
+ }
166
+
167
+ fn draw_interference_ellipse(
168
+ num: usize,
169
+ image: &mut ImageBuffer<Rgb<u8>, Vec<u8>>,
170
+ color: Rgb<u8>,
171
+ ) {
172
+ for _ in 0..num {
173
+ // max cycle width 20px
174
+ let w = (10 + rand_num(10)) as i32;
175
+ let x = rand_num((image.width() - 25) as usize) as i32;
176
+ let y = rand_num((image.height() - 15) as usize) as i32;
177
+
178
+ imageproc::drawing::draw_filled_ellipse_mut(image, (x, y), w, w, color);
179
+ }
180
+ }
181
+
182
+ pub struct Captcha {
183
+ pub text: String,
184
+ pub image: Vec<u8>,
185
+ }
186
+
187
+ pub struct CaptchaBuilder {
188
+ length: usize,
189
+ width: usize,
190
+ height: usize,
191
+ complexity: usize,
192
+ }
193
+
194
+ impl CaptchaBuilder {
195
+ pub fn new() -> Self {
196
+ CaptchaBuilder {
197
+ length: 4,
198
+ width: 220,
199
+ height: 70,
200
+ complexity: 5,
201
+ }
202
+ }
203
+
204
+ pub fn length(mut self, length: usize) -> Self {
205
+ self.length = length;
206
+ self
207
+ }
208
+
209
+ // pub fn width(mut self, width: usize) -> Self {
210
+ // self.width = width;
211
+ // self
212
+ // }
213
+
214
+ // pub fn height(mut self, height: usize) -> Self {
215
+ // self.height = height;
216
+ // self
217
+ // }
218
+
219
+ pub fn complexity(mut self, complexity: usize) -> Self {
220
+ let mut complexity = complexity;
221
+ if complexity > 10 {
222
+ complexity = 10;
223
+ }
224
+ if complexity < 1 {
225
+ complexity = 1;
226
+ }
227
+ self.complexity = complexity;
228
+ self
229
+ }
230
+
231
+ pub fn build(self) -> Captcha {
232
+ // Generate an array of captcha characters
233
+ let res = get_captcha(self.length);
234
+
235
+ let text = res.join("");
236
+
237
+ // Create a white background image
238
+ let mut image = get_image(self.width, self.height);
239
+
240
+ // Loop to write the verification code string into the background image
241
+ cyclic_write_character(&res, &mut image);
242
+
243
+ gaussian_noise_mut(
244
+ &mut image,
245
+ (self.complexity - 1) as f64,
246
+ ((10 * self.complexity) - 10) as f64,
247
+ ((5 * self.complexity) - 5) as u64,
248
+ );
249
+
250
+ let mut bytes: Vec<u8> = Vec::new();
251
+ image
252
+ .write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Jpeg)
253
+ .unwrap();
254
+
255
+ Captcha { text, image: bytes }
256
+ }
257
+ }
@@ -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
@@ -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.1.4.pre"
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].downcase, 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,100 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rucaptcha
3
+ version: !ruby/object:Gem::Version
4
+ version: 3.1.4.pre
5
+ platform: x86_64-linux-musl
6
+ authors:
7
+ - Jason Lee
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-01-11 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
+ force_ruby_platform: false
28
+ - !ruby/object:Gem::Dependency
29
+ name: rb_sys
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: 0.9.18
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: 0.9.18
42
+ description:
43
+ email: huacnlee@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - README.md
49
+ - Rakefile
50
+ - app/controllers/ru_captcha/captcha_controller.rb
51
+ - config/locales/rucaptcha.de-DE.yml
52
+ - config/locales/rucaptcha.en.yml
53
+ - config/locales/rucaptcha.pt-BR.yml
54
+ - config/locales/rucaptcha.zh-CN.yml
55
+ - config/locales/rucaptcha.zh-TW.yml
56
+ - config/routes.rb
57
+ - ext/rucaptcha/Cargo.lock
58
+ - ext/rucaptcha/Cargo.toml
59
+ - ext/rucaptcha/extconf.rb
60
+ - ext/rucaptcha/fonts/FuzzyBubbles-Regular.ttf
61
+ - ext/rucaptcha/fonts/Handlee-Regular.ttf
62
+ - ext/rucaptcha/src/captcha.rs
63
+ - ext/rucaptcha/src/lib.rs
64
+ - lib/rucaptcha.rb
65
+ - lib/rucaptcha/3.1/rucaptcha.so
66
+ - lib/rucaptcha/3.2/rucaptcha.so
67
+ - lib/rucaptcha/cache.rb
68
+ - lib/rucaptcha/configuration.rb
69
+ - lib/rucaptcha/controller_helpers.rb
70
+ - lib/rucaptcha/engine.rb
71
+ - lib/rucaptcha/errors/configuration.rb
72
+ - lib/rucaptcha/version.rb
73
+ - lib/rucaptcha/view_helpers.rb
74
+ homepage: https://github.com/huacnlee/rucaptcha
75
+ licenses:
76
+ - MIT
77
+ metadata: {}
78
+ post_install_message:
79
+ rdoc_options: []
80
+ require_paths:
81
+ - lib
82
+ required_ruby_version: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: '3.1'
87
+ - - "<"
88
+ - !ruby/object:Gem::Version
89
+ version: 3.3.dev
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">"
93
+ - !ruby/object:Gem::Version
94
+ version: 1.3.1
95
+ requirements: []
96
+ rubygems_version: 3.4.3
97
+ signing_key:
98
+ specification_version: 4
99
+ summary: Captcha Gem for Rails, which generates captcha image by Rust.
100
+ test_files: []