extern crate image;
extern crate qrcodegen;
macro_rules! wifi_auth {
(hidden) => ("WIFI:T:{};S:{};P:{};H:{};;");
(nopass) => ("WIFI:T:nopass;S:{};;");
(nopass_hidden) => ("WIFI:T:nopass;S:{};H:{};;");
() => {
"WIFI:T:{};S:{};P:{};;";
};
}
#[cfg(test)]
mod tests {
use super::code::Credentials;
use super::code::{encode, make_svg, manual_encode};
use qrcodegen::{QrCodeEcc, Version};
#[test]
fn test_credentials() {
assert_eq!(
Credentials::new(Some("test"), Some("password"), Some("WPA2"), false, false).format().unwrap(),
"WIFI:T:WPA2;S:test;P:password;;"
);
}
#[test]
fn test_credentials_escapes() {
assert_eq!(
Credentials::new(Some(r###""foo;bar\baz""###),
Some("randompassword"),
Some("wpa2"),
false, false).format().unwrap(),
r###"WIFI:T:WPA2;S:\"foo\;bar\\baz\";P:randompassword;;"###
);
}
#[test]
fn test_qrcodes() {
let credentials = Credentials::new(Some("test"), Some("WPA"), Some("test"), false, false);
assert_eq!(
make_svg(&encode(&credentials).unwrap()),
make_svg(&manual_encode(
&credentials,
QrCodeEcc::High,
Version::new(2),
Version::new(15),
None,
))
);
}
#[test]
fn test_hidden_ssid() {
assert_eq!(Credentials::new(Some(r###""foo;bar\baz""###),
Some("randompassword"),
Some("WPA2"), true, false).format().unwrap(),
r###"WIFI:T:WPA2;S:\"foo\;bar\\baz\";P:randompassword;H:true;;"###);
}
#[test]
fn test_normal_ssid() {
assert_eq!(Credentials::new(Some(r###""foo;bar\baz""###),
Some("randompassword"),
Some("WPA2"), false, false).format().unwrap(),
r###"WIFI:T:WPA2;S:\"foo\;bar\\baz\";P:randompassword;;"###);
}
#[test]
fn test_nopassword_with_wpa2() {
assert!(Credentials::new(Some(r###""foo;bar\baz""###),
Some(""),
Some("wpa"),
false, false).format().is_err(),
"wpa2 requires a password");
assert!(Credentials::new(Some(r###""foo;bar\baz""###),
Some(""),
Some("wpa2"),
false, false).format().is_err(),
"wpa2 requires a password");
}
#[test]
fn test_nopassword_with_wep() {
assert!(Credentials::new(Some(r###""foo;bar\baz""###),
Some(""),
Some("wep"),
false, false).format().is_err(),
"wep requires a password");
}
#[test]
fn test_nopassword_with_nopassword() {
assert!(Credentials::new(Some("bane"),
Some(""),
Some("nopass"),
false, false).format().is_ok(),
"nopass specified with a blank password should work");
}
#[test]
fn test_auth_types() {
assert_eq!(
Credentials::new(Some("test"), Some("password"), Some("wep"), false, false).format().unwrap(),
"WIFI:T:WEP;S:test;P:password;;"
);
assert_eq!(
Credentials::new(Some("test"), Some("password"), Some("WPA"), false, false).format().unwrap(),
"WIFI:T:WPA;S:test;P:password;;"
);
assert_eq!(
Credentials::new(Some("test"), Some("password"), Some("wpa2"), false, false).format().unwrap(),
"WIFI:T:WPA2;S:test;P:password;;"
);
assert_eq!(
Credentials::new(Some("test"), Some("password"), Some("wpa3"), false, false).format().unwrap(),
"WIFI:T:WPA3;S:test;P:password;;"
);
assert_eq!(
Credentials::new(Some("test"), Some(""), Some("nopass"), false, false).format().unwrap(),
"WIFI:T:nopass;S:test;;"
);
}
#[test]
fn test_empty_passwords_with_nopass_encr() {
assert!(Credentials::new(Some(r###""foo;bar\baz""###),
Some("password"),
Some("nopass"),
false, false).format().is_err(),
"nopass cannot be specified with a password");
}
#[test]
fn test_encr_nopass_with_empty_password() {
assert_eq!(
Credentials::new(Some("test"), Some(""), Some("nopass"), false, false).format().unwrap(),
"WIFI:T:nopass;S:test;;"
);
}
#[test]
fn test_quoted_ssid_password() {
assert_eq!(
Credentials::new(Some("test"), Some("password"), Some("wpa2"), false, true).format().unwrap(),
"WIFI:T:WPA2;S:\"test\";P:\"password\";;"
);
}
}
pub mod code {
use std::convert::TryInto;
use image::{ImageBuffer, LumaA};
use qrcodegen::{DataTooLong, Mask, QrCode, QrCodeEcc, QrSegment};
use imageproc::drawing::draw_filled_rect_mut;
use imageproc::rect::Rect;
#[derive(Debug)]
pub struct Credentials {
pub ssid: String,
pub pass: String,
pub encr: String,
pub hidden: bool,
pub quote: bool,
}
impl Credentials {
pub fn new(
mut _ssid: Option<&str>,
mut _password: Option<&str>,
mut _encr: Option<&str>,
mut _hidden: bool,
mut _quote: bool,
) -> Self {
return Credentials {
ssid: _ssid.unwrap().to_string(),
encr: _encr.unwrap().to_string(),
pass: _password.unwrap().to_string(),
hidden: _hidden,
quote: _quote,
};
}
fn filter_credentials(&self, field: &str) -> String {
let mut filtered = field.to_string()
.replace(r#"\"#, r#"\\"#)
.replace(r#"""#, r#"\""#)
.replace(r#";"#, r#"\;"#)
.replace(r#":"#, r#"\:"#);
if (filtered == self.ssid || filtered == self.pass) && self.quote {
filtered = format!("\"{}\"", field.to_string());
}
return filtered
}
fn filter_encr(&self, field: &str) -> String {
if field != "nopass" && !self.encr.is_empty() {
return field.to_string().to_uppercase();
}
return field.to_string();
}
pub fn format(&self) -> Result<String, &'static str> {
if self.encr == "nopass" || self.encr.is_empty() {
if !self.pass.is_empty() {
return Err("With nopass as the encryption type (or unset encryption type),
the password field should be empty. (Encryption should probably be set
to something like wpa2)")
}
}
if self.pass.is_empty() || self.pass == "" {
if self.encr != "nopass" && !self.encr.is_empty() {
return Err("The encryption method requested requires a password.
If you would like no password, set '--encr nopass'")
}
if self.encr.is_empty() || self.encr == "nopass" {
if self.hidden {
return Ok(format!(
wifi_auth!(nopass_hidden),
self.filter_credentials(&self.ssid),
&self.hidden,
));
} else if self.pass == "" {
return Ok(format!(
wifi_auth!(nopass),
self.filter_credentials(&self.ssid),
));
}
}
}
if self.hidden {
return Ok(format!(
wifi_auth!(hidden),
self.filter_credentials(&self.filter_encr(&self.encr)),
self.filter_credentials(&self.ssid),
self.filter_credentials(&self.pass),
&self.hidden,
))
} else {
return Ok(format!(
wifi_auth!(),
self.filter_credentials(&self.filter_encr(&self.encr)),
self.filter_credentials(&self.ssid),
self.filter_credentials(&self.pass)
))
}
}
pub fn format_vec(&self) -> Vec<char> {
return Credentials::format(&self).unwrap().chars().collect();
}
}
pub fn auth(_ssid: Option<&str>, _password: Option<&str>, _encr: Option<&str>,
_hidden: bool, _quote: bool) -> Credentials {
return self::Credentials::new(_ssid, _password, _encr, _hidden, _quote);
}
pub fn encode(config: &Credentials) -> Result<QrCode, DataTooLong> {
let q = QrCode::encode_text(&config.format().unwrap(), QrCodeEcc::High)?;
Ok(q)
}
pub fn manual_encode(config: &Credentials, error_level: QrCodeEcc, lowest_version: qrcodegen::Version,
highest_version: qrcodegen::Version, mask_level: Option<Mask>) -> QrCode {
let wifi: Vec<char> = config.format_vec();
let segs: Vec<QrSegment> = QrSegment::make_segments(&wifi);
return QrCode::encode_segments_advanced(
&segs,
error_level,
lowest_version,
highest_version,
mask_level,
true,
).unwrap();
}
pub fn make_svg(qrcode: &QrCode) -> String {
return qrcode.to_svg_string(4);
}
pub fn console_qr(qrcode: &QrCode, quiet_zone: i32) {
const ASCII_BL_BLOCK: &str = " ";
const ASCII_W_BLOCK: &str = "██";
let x_zone = quiet_zone;
let y_zone = quiet_zone;
for _top_border in 0..y_zone {
print!("{}", ASCII_BL_BLOCK);
println!();
}
for y in 0..qrcode.size() {
for _left_border in 0..x_zone {
print!("{}", ASCII_BL_BLOCK);
}
for x in 0..qrcode.size() {
if qrcode.get_module(x, y) {
print!("{}", ASCII_W_BLOCK);
} else {
print!("{}", ASCII_BL_BLOCK);
}
}
for _right_border in 0..x_zone {
print!("{}", ASCII_BL_BLOCK);
}
println!();
}
for _bottom_border in 0..y_zone {
print!("{}", ASCII_BL_BLOCK);
println!();
}
}
pub fn make_image(qrcode: &QrCode, scale: i32, border_size: i32) -> ImageBuffer<LumaA<u8>, Vec<u8>> {
let new_qr_size = qrcode.size() * scale;
let mut image = ImageBuffer::from_pixel(
(new_qr_size + border_size * 2).try_into().unwrap(),
(new_qr_size + border_size * 2).try_into().unwrap(),
LumaA([255, 255]),
);
for y in 0..new_qr_size {
for x in 0..new_qr_size {
if qrcode.get_module(x, y) {
draw_filled_rect_mut(
&mut image,
Rect::at(
(x * scale) + border_size as i32,
(y * scale) + border_size as i32,
).of_size(scale as u32, scale as u32),
LumaA([0, 255]),
);
} else {
draw_filled_rect_mut(
&mut image,
Rect::at(
(x * scale) + border_size as i32,
(y * scale) + border_size as i32,
).of_size(scale as u32, scale as u32),
LumaA([255, 255]),
);
}
}
}
return image;
}
pub fn save_image(image: &ImageBuffer<LumaA<u8>, Vec<u8>>, save_file: String) {
let _ = image.save(save_file).unwrap();
}
}