daqing_rucaptcha 3.2.1

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.
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
+ }