1extern crate image;
4extern crate qrcodegen;
5
6mod exporters;
7
8macro_rules! wifi_auth {
9 (hidden) => {
17 "WIFI:T:{};S:{};P:{};H:{};;"
18 };
19 (nopass) => {
20 "WIFI:T:nopass;S:{};;"
21 };
22 (nopass_hidden) => {
23 "WIFI:T:nopass;S:{};H:{};;"
24 };
25 () => {
26 "WIFI:T:{};S:{};P:{};;"
27 };
28}
29
30#[cfg(test)]
31mod tests {
32 use super::code::Credentials;
33 use super::code::{encode, make_svg, manual_encode};
34 use qrcodegen::{QrCodeEcc, Version};
35
36 #[test]
38 fn test_credentials() {
39 assert_eq!(
40 Credentials::new(Some("test"), Some("password"), Some("WPA2"), false, false)
41 .format()
42 .unwrap(),
43 "WIFI:T:WPA2;S:test;P:password;;"
44 );
45 }
46
47 #[test]
49 fn test_credentials_escapes() {
50 assert_eq!(
51 Credentials::new(
52 Some(r###""foo;bar\baz""###),
53 Some("randompassword"),
54 Some("wpa2"),
55 false,
56 false
57 )
58 .format()
59 .unwrap(),
60 r###"WIFI:T:WPA2;S:\"foo\;bar\\baz\";P:randompassword;;"###
61 );
62 }
63
64 #[test]
66 fn test_qrcodes() {
67 let credentials = Credentials::new(Some("test"), Some("WPA"), Some("test"), false, false);
68
69 assert_eq!(
70 make_svg(&encode(&credentials).unwrap()),
71 make_svg(&manual_encode(
72 &credentials,
73 QrCodeEcc::High,
74 Version::new(2),
75 Version::new(15),
76 None,
77 ))
78 );
79 }
80
81 #[test]
83 fn test_hidden_ssid() {
84 assert_eq!(
85 Credentials::new(
86 Some(r###""foo;bar\baz""###),
87 Some("randompassword"),
88 Some("WPA2"),
89 true,
90 false
91 )
92 .format()
93 .unwrap(),
94 r###"WIFI:T:WPA2;S:\"foo\;bar\\baz\";P:randompassword;H:true;;"###
95 );
96 }
97
98 #[test]
100 fn test_normal_ssid() {
101 assert_eq!(
102 Credentials::new(
103 Some(r###""foo;bar\baz""###),
104 Some("randompassword"),
105 Some("WPA2"),
106 false,
107 false
108 )
109 .format()
110 .unwrap(),
111 r###"WIFI:T:WPA2;S:\"foo\;bar\\baz\";P:randompassword;;"###
112 );
113 }
114
115 #[test]
117 fn test_nopassword_with_wpa2() {
118 assert!(
119 Credentials::new(
120 Some(r###""foo;bar\baz""###),
121 Some(""),
122 Some("wpa"),
123 false,
124 false
125 )
126 .format()
127 .is_err(),
128 "wpa2 requires a password"
129 );
130
131 assert!(
132 Credentials::new(
133 Some(r###""foo;bar\baz""###),
134 Some(""),
135 Some("wpa2"),
136 false,
137 false
138 )
139 .format()
140 .is_err(),
141 "wpa2 requires a password"
142 );
143 }
144
145 #[test]
147 fn test_nopassword_with_wep() {
148 assert!(
149 Credentials::new(
150 Some(r###""foo;bar\baz""###),
151 Some(""),
152 Some("wep"),
153 false,
154 false
155 )
156 .format()
157 .is_err(),
158 "wep requires a password"
159 );
160 }
161
162 #[test]
163 fn test_nopassword_with_nopassword() {
164 assert!(
165 Credentials::new(Some("bane"), Some(""), Some("nopass"), false, false)
166 .format()
167 .is_ok(),
168 "nopass specified with a blank password should work"
169 );
170 }
171
172 #[test]
174 fn test_auth_types() {
175 assert_eq!(
177 Credentials::new(Some("test"), Some("password"), Some("wep"), false, false)
178 .format()
179 .unwrap(),
180 "WIFI:T:WEP;S:test;P:password;;"
181 );
182
183 assert_eq!(
185 Credentials::new(Some("test"), Some("password"), Some("WPA"), false, false)
186 .format()
187 .unwrap(),
188 "WIFI:T:WPA;S:test;P:password;;"
189 );
190
191 assert_eq!(
194 Credentials::new(Some("test"), Some("password"), Some("wpa2"), false, false)
195 .format()
196 .unwrap(),
197 "WIFI:T:WPA2;S:test;P:password;;"
198 );
199
200 assert_eq!(
202 Credentials::new(Some("test"), Some("password"), Some("wpa3"), false, false)
203 .format()
204 .unwrap(),
205 "WIFI:T:WPA3;S:test;P:password;;"
206 );
207
208 assert_eq!(
210 Credentials::new(Some("test"), Some(""), Some("nopass"), false, false)
211 .format()
212 .unwrap(),
213 "WIFI:T:nopass;S:test;;"
214 );
215 }
216
217 #[test]
218 fn test_empty_passwords_with_nopass_encr() {
219 assert!(
220 Credentials::new(
221 Some(r###""foo;bar\baz""###),
222 Some("password"),
223 Some("nopass"),
224 false,
225 false
226 )
227 .format()
228 .is_err(),
229 "nopass cannot be specified with a password"
230 );
231 }
232
233 #[test]
235 fn test_encr_nopass_with_empty_password() {
236 assert_eq!(
237 Credentials::new(Some("test"), Some(""), Some("nopass"), false, false)
238 .format()
239 .unwrap(),
240 "WIFI:T:nopass;S:test;;"
241 );
242 }
243
244 #[test]
246 fn test_quoted_ssid_password() {
247 assert_eq!(
248 Credentials::new(Some("test"), Some("password"), Some("wpa2"), false, true)
249 .format()
250 .unwrap(),
251 "WIFI:T:WPA2;S:\"test\";P:\"password\";;"
252 );
253 }
254}
255
256pub mod code {
258 use std::error;
259
260 use image::{ImageBuffer, LumaA};
261 use qrcodegen::{Mask, QrCode, QrCodeEcc, QrSegment};
262
263 use crate::exporters::methods::{
264 make_image as make_image_export, save_image as save_image_export,
265 to_svg_string as to_svg_string_export,
266 };
267
268 #[derive(Debug)]
269 pub struct Credentials {
270 pub ssid: String,
271 pub pass: String,
272 pub encr: String,
273 pub hidden: bool,
274 pub quote: bool,
275 }
276
277 impl Credentials {
278 pub fn new(
279 mut _ssid: Option<&str>,
280 mut _password: Option<&str>,
281 mut _encr: Option<&str>,
282 mut _hidden: bool,
283 mut _quote: bool,
284 ) -> Self {
285 Credentials {
286 ssid: _ssid.unwrap().to_string(),
287 encr: _encr.unwrap().to_string(),
288 pass: _password.unwrap().to_string(),
289 hidden: _hidden,
290 quote: _quote,
291 }
292 }
293
294 fn filter_credentials(&self, field: &str) -> String {
298 let mut filtered = field
302 .to_string()
303 .replace('\\', r#"\\"#)
304 .replace('"', r#"\""#)
305 .replace(';', r#"\;"#)
306 .replace(r#"':'"#, r#"\:"#);
307
308 if (filtered == self.ssid || filtered == self.pass) && self.quote {
309 filtered = format!("\"{}\"", field);
311 }
312
313 filtered
314 }
315
316 fn filter_encr(&self, field: &str) -> String {
320 if field != "nopass" && !self.encr.is_empty() {
321 return field.to_string().to_uppercase();
322 }
323 field.to_string()
324 }
325
326 pub fn format(&self) -> Result<String, FormatError> {
330 if (self.encr == "nopass" || self.encr.is_empty()) && !self.pass.is_empty() {
339 return Err(FormatError(
340 "With nopass as the encryption type (or unset encryption type),
341 the password field should be empty. (Encryption should probably be set
342 to something like wpa2)".to_string(),
343 ));
344 }
345
346 if self.pass.is_empty() {
347 if self.encr != "nopass" && !self.encr.is_empty() {
349 return Err(FormatError("The encryption method requested requires a password.".to_string()));
350 }
351
352 if self.encr.is_empty() || self.encr == "nopass" {
353 if self.hidden {
354 return Ok(format!(
355 wifi_auth!(nopass_hidden),
356 self.filter_credentials(&self.ssid),
357 &self.hidden,
358 ));
359 } else if self.pass.is_empty() {
360 return Ok(format!(
361 wifi_auth!(nopass),
362 self.filter_credentials(&self.ssid),
363 ));
364 }
365 }
366 }
367
368 if self.hidden {
369 return Ok(format!(
370 wifi_auth!(hidden),
371 self.filter_credentials(&self.filter_encr(&self.encr)),
372 self.filter_credentials(&self.ssid),
373 self.filter_credentials(&self.pass),
374 &self.hidden,
375 ));
376 } else {
377 return Ok(format!(
378 wifi_auth!(),
379 self.filter_credentials(&self.filter_encr(&self.encr)),
380 self.filter_credentials(&self.ssid),
381 self.filter_credentials(&self.pass)
382 ));
383 }
384 }
385 }
386
387 pub fn auth(
390 _ssid: Option<&str>,
391 _password: Option<&str>,
392 _encr: Option<&str>,
393 _hidden: bool,
394 _quote: bool,
395 ) -> Credentials {
396 self::Credentials::new(_ssid, _password, _encr, _hidden, _quote)
397 }
398
399 pub fn encode(config: &Credentials) -> Result<QrCode, Box<dyn error::Error>> {
401 let c = match config.format() {
402 Ok(c) => c,
403 Err(e) => return Err(e.into()),
404 };
405
406 match QrCode::encode_text(&c, QrCodeEcc::High) {
407 Ok(qr) => Ok(qr),
408 Err(e) => Err(e.into()),
409 }
410 }
411
412 pub fn manual_encode(
416 config: &Credentials,
417 error_level: QrCodeEcc,
418 lowest_version: qrcodegen::Version,
419 highest_version: qrcodegen::Version,
420 mask_level: Option<Mask>,
421 ) -> QrCode {
422 let segs: Vec<QrSegment> = QrSegment::make_segments(&config.format().unwrap());
423
424 QrCode::encode_segments_advanced(
425 &segs,
426 error_level,
427 lowest_version,
428 highest_version,
429 mask_level,
430 true,
431 )
432 .unwrap()
433 }
434
435 pub fn console_qr(qrcode: &QrCode, quiet_zone: i32) {
442 const ASCII_BL_BLOCK: &str = " ";
443 const ASCII_W_BLOCK: &str = "██";
444
445 let x_zone = quiet_zone;
446 let y_zone = quiet_zone;
447
448 for _top_border in 0..y_zone {
450 print!("{}", ASCII_BL_BLOCK);
451 println!();
452 }
453
454 for y in 0..qrcode.size() {
455 for _left_border in 0..x_zone {
457 print!("{}", ASCII_BL_BLOCK);
458 }
459
460 for x in 0..qrcode.size() {
462 if qrcode.get_module(x, y) {
463 print!("{}", ASCII_W_BLOCK);
464 } else {
465 print!("{}", ASCII_BL_BLOCK);
466 }
467 }
468
469 for _right_border in 0..x_zone {
471 print!("{}", ASCII_BL_BLOCK);
472 }
473
474 println!();
475 }
476
477 for _bottom_border in 0..y_zone {
479 print!("{}", ASCII_BL_BLOCK);
480 println!();
481 }
482 }
483
484 pub fn make_image(
485 qrcode: &QrCode,
486 scale: i32,
487 border_size: i32,
488 ) -> ImageBuffer<LumaA<u8>, Vec<u8>> {
489 make_image_export(qrcode, scale, border_size)
490 }
491
492 pub fn make_svg(qrcode: &QrCode) -> String {
497 to_svg_string_export(qrcode, 4)
498 }
499
500 pub fn save_image(
506 image: &ImageBuffer<LumaA<u8>, Vec<u8>>,
507 save_file: String,
508 ) -> Result<(), image::ImageError> {
509 save_image_export(image, save_file)
510 }
511
512 #[derive(Debug, Clone)]
517 pub struct FormatError(String);
518
519 impl std::error::Error for FormatError {
520 fn description(&self) -> &str {
521 &self.0
522 }
523 }
524
525 impl std::fmt::Display for FormatError {
526 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
527 f.write_str(&self.0)
528 }
529 }
530
531}