Switch to qrcodegen, and update image dependency
Signed-off-by: constantoine <cleo.rebert@gmail.com>
This commit is contained in:
parent
7ecdf7d440
commit
bacf1c4fb4
|
@ -16,7 +16,7 @@ features = [ "qr", "serde_support" ]
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = []
|
default = []
|
||||||
qr = ["qrcode", "image", "base64"]
|
qr = ["qrcodegen", "image", "base64"]
|
||||||
serde_support = ["serde"]
|
serde_support = ["serde"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
@ -26,6 +26,6 @@ sha-1 = "~0.10.0"
|
||||||
hmac = "~0.12.1"
|
hmac = "~0.12.1"
|
||||||
base32 = "~0.4"
|
base32 = "~0.4"
|
||||||
constant_time_eq = "~0.2.1"
|
constant_time_eq = "~0.2.1"
|
||||||
qrcode = { version = "~0.12", optional = true }
|
qrcodegen = { version = "~1.8", optional = true }
|
||||||
image = { version = "~0.23.14", optional = true}
|
image = { version = "~0.24.2", optional = true}
|
||||||
base64 = { version = "~0.13", optional = true }
|
base64 = { version = "~0.13", optional = true }
|
||||||
|
|
77
src/lib.rs
77
src/lib.rs
|
@ -52,7 +52,7 @@ use serde::{Deserialize, Serialize};
|
||||||
use core::fmt;
|
use core::fmt;
|
||||||
|
|
||||||
#[cfg(feature = "qr")]
|
#[cfg(feature = "qr")]
|
||||||
use {base64, image::Luma, qrcode::QrCode};
|
use {base64, image::Luma, qrcodegen};
|
||||||
|
|
||||||
use hmac::Mac;
|
use hmac::Mac;
|
||||||
|
|
||||||
|
@ -206,19 +206,78 @@ impl<T: AsRef<[u8]>> TOTP<T> {
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
///
|
///
|
||||||
/// This will return an error in case the URL gets too long to encode into a QR code
|
/// This will return an error in case the URL gets too long to encode into a QR code.
|
||||||
|
/// This would require the get_url method to generate an url bigger than 2000 characters,
|
||||||
|
/// Which would be too long for some browsers anyway.
|
||||||
///
|
///
|
||||||
/// It will also return an error in case it can't encode the qr into a png. This shouldn't happen unless either the qrcode library returns malformed data, or the image library doesn't encode the data correctly
|
/// It will also return an error in case it can't encode the qr into a png. This shouldn't happen unless either the qrcode library returns malformed data, or the image library doesn't encode the data correctly
|
||||||
#[cfg(feature = "qr")]
|
#[cfg(feature = "qr")]
|
||||||
pub fn get_qr(&self, label: &str, issuer: &str) -> Result<String, Box<dyn std::error::Error>> {
|
pub fn get_qr(&self, label: &str, issuer: &str) -> Result<String, Box<dyn std::error::Error>> {
|
||||||
|
use image::ImageEncoder;
|
||||||
|
|
||||||
let url = self.get_url(label, issuer);
|
let url = self.get_url(label, issuer);
|
||||||
let code = QrCode::new(&url)?;
|
|
||||||
let mut vec = Vec::new();
|
let mut vec = Vec::new();
|
||||||
let encoder = image::png::PngEncoder::new(&mut vec);
|
let qr = qrcodegen::QrCode::encode_text(&url, qrcodegen::QrCodeEcc::Medium)?;
|
||||||
encoder.encode(
|
let size = qr.size() as u32;
|
||||||
code.render::<Luma<u8>>().build().as_ref(),
|
|
||||||
((code.width() + 8) * 8) as u32,
|
// "+ 8 * 8" is here to add padding (the white border around the QRCode)
|
||||||
((code.width() + 8) * 8) as u32,
|
// As some QRCode readers don't work without padding
|
||||||
|
let image_size = size * 8 + 8 * 8;
|
||||||
|
let mut canvas = image::GrayImage::new(image_size, image_size);
|
||||||
|
|
||||||
|
// Draw the border
|
||||||
|
for x in 0..image_size {
|
||||||
|
for y in 0..image_size {
|
||||||
|
if y < 8*4 || y >= image_size - 8*4 {
|
||||||
|
canvas.put_pixel(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
Luma([255]),
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if x < 8*4 || x >= image_size - 8*4 {
|
||||||
|
canvas.put_pixel(
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
Luma([255]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The QR inside the white border
|
||||||
|
for x_qr in 0..size {
|
||||||
|
for y_qr in 0..size {
|
||||||
|
// The canvas is a grayscale image without alpha. Hence it's only one 8-bits byte longs
|
||||||
|
// This clever trick to one-line the value was achieved with advanced mathematics
|
||||||
|
// And deep understanding of Boolean algebra.
|
||||||
|
let val = !qr.get_module(x_qr as i32, y_qr as i32) as u8 * 255;
|
||||||
|
|
||||||
|
// Multiply coordinates by width of pixels
|
||||||
|
// And take into account the 8*4 padding on top and left side
|
||||||
|
let x_start = x_qr * 8 + 8*4;
|
||||||
|
let y_start = y_qr * 8 + 8*4;
|
||||||
|
|
||||||
|
// Draw a 8-pixels-wide square
|
||||||
|
for x_img in x_start..x_start + 8 {
|
||||||
|
for y_img in y_start..y_start + 8 {
|
||||||
|
canvas.put_pixel(
|
||||||
|
x_img,
|
||||||
|
y_img,
|
||||||
|
Luma([val]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Encode the canvas into a PNG
|
||||||
|
let encoder = image::codecs::png::PngEncoder::new(&mut vec);
|
||||||
|
encoder.write_image(
|
||||||
|
&image::ImageBuffer::from(canvas).into_raw(),
|
||||||
|
image_size,
|
||||||
|
image_size,
|
||||||
image::ColorType::L8,
|
image::ColorType::L8,
|
||||||
)?;
|
)?;
|
||||||
Ok(base64::encode(vec))
|
Ok(base64::encode(vec))
|
||||||
|
@ -345,7 +404,7 @@ mod tests {
|
||||||
let hash_digest = Sha1::digest(qr.as_bytes());
|
let hash_digest = Sha1::digest(qr.as_bytes());
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
format!("{:x}", hash_digest).as_str(),
|
format!("{:x}", hash_digest).as_str(),
|
||||||
"3abc0127e7a2b1013fb25c97ef14422c1fe9e878"
|
"f671a5a553227a9565c6132024808123f2c9e5e3"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue