daqing_rucaptcha 3.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +188 -0
  3. data/Rakefile +71 -0
  4. data/app/controllers/ru_captcha/captcha_controller.rb +14 -0
  5. data/config/locales/rucaptcha.de-DE.yml +3 -0
  6. data/config/locales/rucaptcha.en.yml +3 -0
  7. data/config/locales/rucaptcha.pt-BR.yml +3 -0
  8. data/config/locales/rucaptcha.zh-CN.yml +3 -0
  9. data/config/locales/rucaptcha.zh-TW.yml +3 -0
  10. data/config/routes.rb +3 -0
  11. data/ext/rucaptcha/Cargo.lock +1226 -0
  12. data/ext/rucaptcha/Cargo.toml +14 -0
  13. data/ext/rucaptcha/extconf.rb +4 -0
  14. data/ext/rucaptcha/fonts/FuzzyBubbles-Regular.ttf +0 -0
  15. data/ext/rucaptcha/fonts/Handlee-Regular.ttf +0 -0
  16. data/ext/rucaptcha/src/captcha.rs +341 -0
  17. data/ext/rucaptcha/src/lib.rs +30 -0
  18. data/ext/rucaptcha/target/release/build/clang-sys-f87b799a4d35af4b/out/common.rs +355 -0
  19. data/ext/rucaptcha/target/release/build/clang-sys-f87b799a4d35af4b/out/dynamic.rs +257 -0
  20. data/ext/rucaptcha/target/release/build/clang-sys-f87b799a4d35af4b/out/macros.rs +38 -0
  21. data/ext/rucaptcha/target/release/build/num-bigint-34c75daf72c2b049/out/radix_bases.rs +780 -0
  22. data/ext/rucaptcha/target/release/build/rb-sys-6bdd5b2895b9570a/out/bindings-0.9.78-arm64-darwin22-3.2.2.rs +19775 -0
  23. data/ext/rucaptcha/target/release/build/typenum-f3872660002fb991/out/consts.rs +2248 -0
  24. data/ext/rucaptcha/target/release/build/typenum-f3872660002fb991/out/op.rs +1030 -0
  25. data/ext/rucaptcha/target/release/build/typenum-f3872660002fb991/out/tests.rs +20565 -0
  26. data/lib/rucaptcha/cache.rb +12 -0
  27. data/lib/rucaptcha/configuration.rb +21 -0
  28. data/lib/rucaptcha/controller_helpers.rb +90 -0
  29. data/lib/rucaptcha/engine.rb +15 -0
  30. data/lib/rucaptcha/errors/configuration.rb +5 -0
  31. data/lib/rucaptcha/version.rb +3 -0
  32. data/lib/rucaptcha/view_helpers.rb +22 -0
  33. data/lib/rucaptcha.rb +86 -0
  34. metadata +103 -0
@@ -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,341 @@
1
+ use image::{ImageBuffer, Rgba};
2
+ use rand::{thread_rng, Rng};
3
+ use rusttype::{Font, Scale};
4
+ use std::io::Cursor;
5
+
6
+ static BASIC_CHAR: [char; 54] = [
7
+ '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'M',
8
+ 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g',
9
+ 'h', 'j', 'k', 'm', 'n', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
10
+ ];
11
+
12
+ static FONT_BYTES1: &[u8; 145008] = include_bytes!("../fonts/FuzzyBubbles-Regular.ttf");
13
+ static FONT_BYTES2: &[u8; 37792] = include_bytes!("../fonts/Handlee-Regular.ttf");
14
+
15
+ // https://coolors.co/cc0b8f-7c0abe-5700c8-3c2ea4-3d56a8-3fa67e-45bb30-69d003-a0d003-d8db02
16
+ static COLORS: [(u8, u8, u8, u8); 14] = [
17
+ (197, 166, 3, 255),
18
+ (187, 87, 5, 255),
19
+ (176, 7, 7, 255),
20
+ (186, 9, 56, 255),
21
+ (204, 11, 143, 255),
22
+ (124, 10, 190, 255),
23
+ (87, 0, 200, 255),
24
+ (61, 86, 168, 255),
25
+ (63, 166, 126, 255),
26
+ (69, 187, 48, 255),
27
+ (105, 208, 3, 255),
28
+ (160, 208, 3, 255),
29
+ (216, 219, 2, 255),
30
+ (50, 50, 50, 255),
31
+ ];
32
+
33
+ static SCALE_SM: u32 = 32;
34
+ static SCALE_MD: u32 = 45;
35
+ static SCALE_LG: u32 = 55;
36
+
37
+ fn rand_num(len: usize) -> usize {
38
+ let mut rng = thread_rng();
39
+ rng.gen_range(0..=len)
40
+ }
41
+
42
+ fn get_captcha(len: usize) -> Vec<String> {
43
+ let mut res = vec![];
44
+ for _ in 0..len {
45
+ let rnd = rand_num(53);
46
+ res.push(BASIC_CHAR[rnd].to_string())
47
+ }
48
+ res
49
+ }
50
+
51
+ #[allow(unused)]
52
+ fn get_color() -> Rgba<u8> {
53
+ let rnd = rand_num(COLORS.len() - 1);
54
+ let c = COLORS[rnd];
55
+ Rgba([c.0, c.1, c.2, c.3])
56
+ }
57
+
58
+ fn get_colors(num: usize) -> Vec<Rgba<u8>> {
59
+ let rnd = rand_num(COLORS.len());
60
+ let mut out = vec![];
61
+ for i in 0..num {
62
+ let c = COLORS[(rnd + i) % COLORS.len()];
63
+ out.push(Rgba([c.0, c.1, c.2, c.3]))
64
+ }
65
+
66
+ out
67
+ }
68
+
69
+ fn get_next(min: f32, max: u32) -> f32 {
70
+ min + rand_num(max as usize - min as usize) as f32
71
+ }
72
+
73
+ fn get_font() -> Font<'static> {
74
+ match rand_num(2) {
75
+ 0 => Font::try_from_bytes(FONT_BYTES1).unwrap(),
76
+ 1 => Font::try_from_bytes(FONT_BYTES2).unwrap(),
77
+ _ => Font::try_from_bytes(FONT_BYTES1).unwrap(),
78
+ }
79
+ }
80
+
81
+ fn get_image(width: usize, height: usize) -> ImageBuffer<Rgba<u8>, Vec<u8>> {
82
+ ImageBuffer::from_fn(width as u32, height as u32, |_, _| {
83
+ image::Rgba([255, 255, 255, 255])
84
+ })
85
+ }
86
+
87
+ fn cyclic_write_character(res: &[String], image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, lines: bool) {
88
+ let c = (image.width() - 20) / res.len() as u32;
89
+ let y = image.height() / 3 - 15;
90
+
91
+ let h = image.height() as f32;
92
+
93
+ let scale = match res.len() {
94
+ 1..=3 => SCALE_LG,
95
+ 4..=5 => SCALE_MD,
96
+ _ => SCALE_SM,
97
+ } as f32;
98
+
99
+ let colors = get_colors(res.len());
100
+ let line_colors = get_colors(res.len());
101
+
102
+ let xscale = scale - rand_num((scale * 0.2) as usize) as f32;
103
+ let yscale = h as f32 - rand_num((h * 0.2) as usize) as f32;
104
+
105
+ // Draw line, ellipse first as background
106
+ for (i, _) in res.iter().enumerate() {
107
+ let line_color = line_colors[i];
108
+
109
+ if lines {
110
+ draw_interference_line(1, image, line_color);
111
+ }
112
+ draw_interference_ellipse(1, image, line_color);
113
+ }
114
+
115
+ // Draw text
116
+ for (i, _) in res.iter().enumerate() {
117
+ let text = &res[i];
118
+
119
+ let color = colors[i];
120
+ let font = get_font();
121
+
122
+ for j in 0..(rand_num(3) + 1) as i32 {
123
+ // Draw text again with offset
124
+ let offset = j * (rand_num(2) as i32);
125
+ imageproc::drawing::draw_text_mut(
126
+ image,
127
+ color,
128
+ 10 + offset + (i as u32 * c) as i32,
129
+ y as i32 as i32,
130
+ Scale {
131
+ x: xscale + offset as f32,
132
+ y: yscale as f32,
133
+ },
134
+ &font,
135
+ text,
136
+ );
137
+ }
138
+ }
139
+ }
140
+
141
+ fn draw_interference_line(num: usize, image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>, color: Rgba<u8>) {
142
+ for _ in 0..num {
143
+ let width = image.width();
144
+ let height = image.height();
145
+ let x1: f32 = 5.0;
146
+ let y1 = get_next(x1, height / 2);
147
+
148
+ let x2 = (width - 5) as f32;
149
+ let y2 = get_next(5.0, height - 5);
150
+
151
+ let ctrl_x = get_next((width / 6) as f32, width / 4 * 3);
152
+ let ctrl_y = get_next(x1, height - 5);
153
+
154
+ let ctrl_x2 = get_next((width / 12) as f32, width / 12 * 3);
155
+ let ctrl_y2 = get_next(x1, height - 5);
156
+ // Randomly draw bezier curves
157
+ imageproc::drawing::draw_cubic_bezier_curve_mut(
158
+ image,
159
+ (x1, y1),
160
+ (x2, y2),
161
+ (ctrl_x, ctrl_y),
162
+ (ctrl_x2, ctrl_y2),
163
+ color,
164
+ );
165
+ }
166
+ }
167
+
168
+ fn draw_interference_ellipse(
169
+ num: usize,
170
+ image: &mut ImageBuffer<Rgba<u8>, Vec<u8>>,
171
+ color: Rgba<u8>,
172
+ ) {
173
+ for _ in 0..num {
174
+ // max cycle width 20px
175
+ let w = (10 + rand_num(10)) as i32;
176
+ let x = rand_num((image.width() - 25) as usize) as i32;
177
+ let y = rand_num((image.height() - 15) as usize) as i32;
178
+
179
+ imageproc::drawing::draw_filled_ellipse_mut(image, (x, y), w, w, color);
180
+ }
181
+ }
182
+
183
+ pub struct Captcha {
184
+ pub text: String,
185
+ pub image: Vec<u8>,
186
+ }
187
+
188
+ pub struct CaptchaBuilder {
189
+ length: usize,
190
+ width: usize,
191
+ height: usize,
192
+ complexity: usize,
193
+ line: bool,
194
+ noise: bool,
195
+ format: image::ImageFormat,
196
+ }
197
+
198
+ impl CaptchaBuilder {
199
+ pub fn new() -> Self {
200
+ CaptchaBuilder {
201
+ length: 4,
202
+ width: 220,
203
+ height: 70,
204
+ complexity: 5,
205
+ line: true,
206
+ noise: false,
207
+ format: image::ImageFormat::Png,
208
+ }
209
+ }
210
+
211
+ pub fn length(mut self, length: usize) -> Self {
212
+ self.length = length;
213
+ self
214
+ }
215
+
216
+ pub fn line(mut self, line: bool) -> Self {
217
+ self.line = line;
218
+ self
219
+ }
220
+
221
+ pub fn noise(mut self, noise: bool) -> Self {
222
+ self.noise = noise;
223
+ self
224
+ }
225
+
226
+ pub fn format(mut self, format: &str) -> Self {
227
+ self.format = match format {
228
+ "png" => image::ImageFormat::Png,
229
+ "jpg" | "jpeg" => image::ImageFormat::Jpeg,
230
+ "webp" => image::ImageFormat::WebP,
231
+ _ => image::ImageFormat::Png,
232
+ };
233
+
234
+ self
235
+ }
236
+
237
+ pub fn complexity(mut self, complexity: usize) -> Self {
238
+ let mut complexity = complexity;
239
+ if complexity > 10 {
240
+ complexity = 10;
241
+ }
242
+ if complexity < 1 {
243
+ complexity = 1;
244
+ }
245
+ self.complexity = complexity;
246
+ self
247
+ }
248
+
249
+ pub fn build(self) -> Captcha {
250
+ // Generate an array of captcha characters
251
+ let res = get_captcha(self.length);
252
+
253
+ let text = res.join("");
254
+
255
+ // Create a white background image
256
+ let mut image = get_image(self.width, self.height);
257
+
258
+ // Loop to write the verification code string into the background image
259
+ cyclic_write_character(&res, &mut image, self.line);
260
+
261
+ if self.noise {
262
+ imageproc::noise::gaussian_noise_mut(
263
+ &mut image,
264
+ (self.complexity - 1) as f64,
265
+ ((10 * self.complexity) - 10) as f64,
266
+ ((5 * self.complexity) - 5) as u64,
267
+ );
268
+ }
269
+
270
+ let mut bytes: Vec<u8> = Vec::new();
271
+ image
272
+ .write_to(&mut Cursor::new(&mut bytes), image::ImageFormat::Png)
273
+ .unwrap();
274
+
275
+ Captcha { text, image: bytes }
276
+ }
277
+ }
278
+
279
+ #[cfg(test)]
280
+ mod tests {
281
+ use super::*;
282
+
283
+ #[test]
284
+ fn test_format() {
285
+ let mut builder = CaptchaBuilder::new();
286
+ assert_eq!(builder.format, image::ImageFormat::Png);
287
+
288
+ builder = builder.format("jpg");
289
+ assert_eq!(builder.format, image::ImageFormat::Jpeg);
290
+ builder = builder.format("jpeg");
291
+ assert_eq!(builder.format, image::ImageFormat::Jpeg);
292
+ builder = builder.format("webp");
293
+ assert_eq!(builder.format, image::ImageFormat::WebP);
294
+ builder = builder.format("png");
295
+ assert_eq!(builder.format, image::ImageFormat::Png);
296
+ builder = builder.format("gif");
297
+ assert_eq!(builder.format, image::ImageFormat::Png);
298
+ }
299
+
300
+ #[test]
301
+ fn test_line() {
302
+ let mut builder = CaptchaBuilder::new();
303
+ assert!(builder.line);
304
+
305
+ builder = builder.line(false);
306
+ assert!(!builder.line);
307
+ }
308
+
309
+ #[test]
310
+ fn test_noise() {
311
+ let mut builder = CaptchaBuilder::new();
312
+ assert!(!builder.noise);
313
+
314
+ builder = builder.noise(true);
315
+ assert!(builder.noise);
316
+ }
317
+
318
+ #[test]
319
+ fn test_difficulty() {
320
+ let mut builder = CaptchaBuilder::new();
321
+ assert_eq!(builder.complexity, 5);
322
+
323
+ builder = builder.complexity(10);
324
+ assert_eq!(builder.complexity, 10);
325
+
326
+ builder = builder.complexity(11);
327
+ assert_eq!(builder.complexity, 10);
328
+
329
+ builder = builder.complexity(0);
330
+ assert_eq!(builder.complexity, 1);
331
+ }
332
+
333
+ #[test]
334
+ fn test_length() {
335
+ let mut builder = CaptchaBuilder::new();
336
+ assert_eq!(builder.length, 4);
337
+
338
+ builder = builder.length(10);
339
+ assert_eq!(builder.length, 10);
340
+ }
341
+ }
@@ -0,0 +1,30 @@
1
+ use magnus::{define_class, function, Error, Object};
2
+
3
+ mod captcha;
4
+
5
+ pub fn create(
6
+ len: usize,
7
+ difficulty: usize,
8
+ line: bool,
9
+ noise: bool,
10
+ format: String,
11
+ ) -> (String, Vec<u8>) {
12
+ let c = captcha::CaptchaBuilder::new();
13
+ let out = c
14
+ .complexity(difficulty)
15
+ .length(len)
16
+ .line(line)
17
+ .noise(noise)
18
+ .format(&format)
19
+ .build();
20
+
21
+ (out.text, out.image)
22
+ }
23
+
24
+ #[magnus::init]
25
+ fn init() -> Result<(), Error> {
26
+ let class = define_class("RuCaptchaCore", Default::default())?;
27
+ class.define_singleton_method("create", function!(create, 5))?;
28
+
29
+ Ok(())
30
+ }