#![warn(missing_docs)]
extern crate crypto;
mod des;
#[macro_use]
mod util;
use crypto::sha1::Sha1;
use crypto::digest::Digest;
use hash::*;
use util::*;
use std::{io, mem};
use std::io::Write;
pub mod hash;
pub struct Fourchan;
pub struct FourchanNonescaping;
pub struct Mona;
pub struct MonaNonescaping;
pub struct Mona10;
pub use FourchanNonescaping as Mona10Nonescaping;
pub struct Mona12;
pub struct Mona12Nonescaping;
pub struct MonaRaw;
pub struct Sc;
pub struct ScSjis;
pub struct Sc15;
pub struct ScKatakana;
pub trait TripcodeGenerator {
type Hash: TripcodeHash;
fn hash<P: AsRef<[u8]>>(password: &P) -> Self::Hash;
#[inline]
fn generate<P: AsRef<[u8]>>(password: &P) -> String {
Self::hash(password).encode()
}
#[inline]
fn append<P: AsRef<[u8]>>(password: &P, dst: &mut String) {
Self::hash(password).append(dst);
}
#[inline]
fn write<P, W>(password: &P, dst: &mut W) -> io::Result<()> where P: AsRef<[u8]>, W: Write {
Self::hash(password).write(dst)
}
#[inline]
fn generate_sjis<P: AsRef<[u8]>>(password: &P) -> Vec<u8> {
Self::hash(password).encode_to_sjis()
}
#[inline]
fn append_sjis<P: AsRef<[u8]>>(password: &P, dst: &mut Vec<u8>) {
Self::hash(password).append_sjis(dst)
}
#[inline]
fn write_sjis<P, W>(password: &P, dst: &mut W) -> io::Result<()>
where P: AsRef<[u8]>, W: Write
{
Self::hash(password).write_sjis(dst)
}
}
pub trait TripcodeGeneratorFailable {
type Hash: TripcodeHash;
fn try_hash<P: AsRef<[u8]>>(password: &P) -> Option<Self::Hash>;
#[inline]
fn try_generate<P: AsRef<[u8]>>(password: &P) -> Option<String> {
Self::try_hash(password).map(|h| h.encode())
}
#[inline]
fn try_append<P: AsRef<[u8]>>(password: &P, dst: &mut String) -> Option<()> {
Self::try_hash(password).map(|h| h.append(dst))
}
#[inline]
fn try_write<P, W>(password: &P, dst: &mut W) -> Option<io::Result<()>>
where P: AsRef<[u8]>, W: Write
{
Self::try_hash(password).map(|h| h.write(dst))
}
#[inline]
fn try_generate_sjis<P: AsRef<[u8]>>(password: &P) -> Option<Vec<u8>> {
Self::try_hash(password).map(|h| h.encode_to_sjis())
}
#[inline]
fn try_append_sjis<P: AsRef<[u8]>>(password: &P, dst: &mut Vec<u8>) -> Option<()>
{
Self::try_hash(password).map(|h| h.append_sjis(dst))
}
#[inline]
fn try_write_sjis<P, W>(password: &P, dst: &mut W) -> Option<io::Result<()>>
where P: AsRef<[u8]>, W: Write
{
Self::try_hash(password).map(|h| h.write_sjis(dst))
}
}
impl<T> TripcodeGeneratorFailable for T where T: TripcodeGenerator {
type Hash = <Self as TripcodeGenerator>::Hash;
#[inline]
fn try_hash<P: AsRef<[u8]>>(password: &P) -> Option<Self::Hash> {
Some(Self::hash(password))
}
}
macro_rules! des_cipher_escaped {
($password:expr, $escaper:ident) => {{
let mut key = 0u64;
let mut salt = 0u32;
let mut j = 0; for &c in $password {
$escaper!(c, |escaped| {
key |= pack_u64_be(escaped) >> 8*j;
match j {
0 => salt = decode_salt(escaped[1], escaped[2]),
1 => salt = decode_salt(escaped[0], escaped[1]),
2 => salt |= decode_salt(b'.', escaped[0]),
_ => (),
}
j += escaped.len();
}, || {
key |= (c as u64) << (8*(7-j));
salt |= decode_salt_char(c, j);
j += 1;
});
if j >= 8 {
break;
}
}
match j {
0 | 1 => salt = decode_salt(b'H', b'.'),
2 => salt |= decode_salt(b'.', b'H'),
_ => (),
}
key = key << 1 & 0xFEFE_FEFE_FEFE_FEFE;
des::zero_cipher_58(key, salt)
}};
}
impl TripcodeGenerator for Fourchan {
type Hash = FourchanHash;
fn hash<P: AsRef<[u8]>>(password: &P) -> Self::Hash {
FourchanHash(des_cipher_escaped!(password.as_ref(), fourchan_escape))
}
}
impl TripcodeGenerator for FourchanNonescaping {
type Hash = FourchanHash;
fn hash<P: AsRef<[u8]>>(password: &P) -> Self::Hash {
let password = password.as_ref();
let salt = match password.len() {
0 | 1 => decode_salt(b'H', b'.'),
2 => decode_salt(password[1], b'H'),
_ => decode_salt(password[1], password[2]),
};
FourchanHash(des::zero_cipher_58(secret_to_key(password), salt))
}
}
fn mona_internal<P, H, I>(password: &P, escape: bool) -> MonaHash
where P: AsRef<[u8]>, H: TripcodeGenerator<Hash=Mona10Hash>, I: TripcodeGenerator<Hash=Mona12Hash>
{
use hash::MonaHash::*;
let as_ref = password.as_ref();
let len = if escape {
as_ref.into_iter()
.map(|&c| mona_escape!(c, |escaped| escaped.len(), || 1 as usize))
.sum()
} else {
as_ref.len()
};
if len >= 12 {
let sign = as_ref[0];
if sign == b'#' {
match MonaRaw::try_hash(password) {
Some(h) => Ten(h),
None => Error,
}
} else if sign == b'$' {
Error
} else {
Twelve(I::hash(password))
}
} else {
Ten(H::hash(password))
}
}
impl TripcodeGenerator for Mona {
type Hash = MonaHash;
fn hash<P: AsRef<[u8]>>(password: &P) -> Self::Hash {
mona_internal::<_, Mona10, Mona12>(password, true)
}
}
impl TripcodeGenerator for MonaNonescaping {
type Hash = MonaHash;
fn hash<P: AsRef<[u8]>>(password: &P) -> Self::Hash {
mona_internal::<_, Mona10Nonescaping, Mona12Nonescaping>(password, false)
}
}
impl TripcodeGenerator for Mona10 {
type Hash = Mona10Hash;
fn hash<P: AsRef<[u8]>>(password: &P) -> Self::Hash {
Mona10Hash(des_cipher_escaped!(password.as_ref(), mona_escape))
}
}
impl TripcodeGeneratorFailable for MonaRaw {
type Hash = Mona10Hash;
fn try_hash<P: AsRef<[u8]>>(password: &P) -> Option<Mona10Hash> {
let password = password.as_ref();
macro_rules! try_dec {
($c1:expr, $c2:expr) => {
match decode_salt_strict($c1, $c2) {
Some(s) => s,
None => return None,
}
}
}
let salt = match password.len() {
17 => 0,
18 => try_dec!(password[17], b'.'),
19 => try_dec!(password[17], password[18]),
_ => return None,
};
macro_rules! try_hex {
($c:expr) => {
match hex_to_i($c) {
x @ 0...0xF => x,
_ => return None,
}
}
}
let mut packed = [0u8; 8];
for (i, b) in packed.iter_mut().enumerate() {
let (d1, d0) = (password[2*i+1], password[2*i+2]);
let byte = (try_hex!(d1) << 4) | try_hex!(d0);
if byte == 0 { break; }
*b = byte;
}
Some(Mona10Hash(des::zero_cipher_58(secret_to_key(&packed), salt)))
}
}
fn sha1_internal<T, F>(password: &[u8], escape: bool, result: F) -> T
where F: Fn(&[u8; 20]) -> T
{
let mut sha1 = Sha1::new();
let mut digest: [u8; 20] = unsafe { mem::uninitialized() };
if escape {
let mut first = 0;
for (i, &c) in password.iter().enumerate() {
mona_escape!(c, |escaped| {
sha1.input(&password[first..i]);
sha1.input(escaped);
first = i+1;
}, || ());
}
sha1.input(&password[first..]);
} else {
sha1.input(password);
}
sha1.result(&mut digest);
result(&digest)
}
impl TripcodeGenerator for Mona12 {
type Hash = Mona12Hash;
fn hash<P: AsRef<[u8]>>(password: &P) -> Mona12Hash {
sha1_internal(password.as_ref(), true, |d| unsafe {
Mona12Hash((*(d.as_ptr() as *const u64)).to_be(), d[8])
})
}
}
impl TripcodeGenerator for Mona12Nonescaping {
type Hash = Mona12Hash;
fn hash<P: AsRef<[u8]>>(password: &P) -> Mona12Hash {
sha1_internal(password.as_ref(), false, |d| unsafe {
Mona12Hash((*(d.as_ptr() as *const u64)).to_be(), d[8])
})
}
}
fn sc_internal<P, F>(password: &P, katakana: F) -> ScHash
where P: AsRef<[u8]>, F: Fn(&[u8]) -> bool
{
use hash::ScHash::*;
let as_ref = password.as_ref();
if as_ref.len() >= 12 {
let sign = as_ref[0];
if sign == b'#' {
match MonaRaw::try_hash(password) {
Some(h) => Ten(h),
None => Error,
}
} else if sign == b'$' {
let h = Sc15::hash(password);
if katakana(as_ref) {
Katakana(ScKatakanaHash(h))
} else {
Fifteen(h)
}
} else {
Twelve(Mona12Nonescaping::hash(password))
}
} else {
Ten(FourchanNonescaping::hash(password))
}
}
impl TripcodeGenerator for Sc {
type Hash = ScHash;
fn hash<P: AsRef<[u8]>>(password: &P) -> ScHash {
sc_internal(password, sc_password_starts_with_katakana)
}
}
impl TripcodeGenerator for ScSjis {
type Hash = ScHash;
fn hash<P: AsRef<[u8]>>(password: &P) -> ScHash {
sc_internal(password, |slice| {
let first = slice[1];
0xA1 <= first && first <= 0xDF }
)
}
}
impl TripcodeGenerator for Sc15 {
type Hash = Sc15Hash;
fn hash<P: AsRef<[u8]>>(password: &P) -> Sc15Hash {
sha1_internal(&password.as_ref(), false, |d| unsafe {
Sc15Hash(
u64::from_be(*(d.as_ptr().offset(2) as *const u64)) << 2 & 0xFFFF_FFFF_FFFF_FFF0,
(u64::from_be(*(d.as_ptr().offset(6) as *const u64)) >> 2 & 0xFFFF_FFFC) as u32
)
})
}
}
impl TripcodeGenerator for ScKatakana {
type Hash = ScKatakanaHash;
fn hash<P: AsRef<[u8]>>(password: &P) -> ScKatakanaHash {
ScKatakanaHash(Sc15::hash(password))
}
}
#[cfg(test)]
mod tests {
extern crate encoding;
use self::encoding::all::WINDOWS_31J as SJIS;
use self::encoding::{Encoding, EncoderTrap};
use super::*;
macro_rules! assert_tripcode_eq {
($expected:expr, $password:expr) => {
assert_tripcode_eq!($expected, $password, Mona);
};
($expected:expr, $password:expr, $hasher:ty) => {{
let tripcode = <$hasher>::generate(&$password);
assert_tripcode_eq!(cmp $expected, tripcode, $password);
let mut tripcode = String::with_capacity(45);
<$hasher>::append(&$password, &mut tripcode);
assert_tripcode_eq!(cmp $expected, &tripcode, $password);
let mut tripcode = [0u8; 15];
<$hasher>::write(&$password, &mut (&mut tripcode as &mut [u8])).unwrap();
assert_tripcode_eq!(cmp $expected, String::from_utf8_lossy(&tripcode[..$expected.len()]), $password);
}};
(cmp $expected:expr, $tripcode:expr, $password:expr) => {
assert!($expected == $tripcode,
"tripcode mismatched: expected: `{:?}`, tripcode: `{:?}`, password: `{:?}`",
$expected, $tripcode, $password)
};
}
#[test]
fn mona_10_matches() {
assert_tripcode_eq!("jPpg5.obl6", r"");
assert_tripcode_eq!("nOA3ItxPxI", r"k");
assert_tripcode_eq!("GDsuFp4oF6", r"[K");
assert_tripcode_eq!("IG4wjn.Cxc", r"2 V");
assert_tripcode_eq!("P97zJ5IHPI", r"|TB~");
assert_tripcode_eq!(".HIpR.ZMqM", r"(~A5|");
assert_tripcode_eq!("9zAZPOvSZI", r"[6??Pz");
assert_tripcode_eq!("KgeLOKK0NQ", r"ErdxpJ$");
assert_tripcode_eq!("BX6/llcs1o", r"dib3Q_4x");
assert_tripcode_eq!("6trdEPfEr6", r"R%!IuxM.t");
}
#[test]
fn mona_raw_matches() {
assert_tripcode_eq!("IP9Lda5FPc", "#0123456789abcdef./");
assert_tripcode_eq!("7Uzd/KllpE", "#FF00000000000000");
assert_tripcode_eq!("7Uzd/KllpE", "#FF00FFFFFFFFFFFF");
}
#[test]
fn mona_12_matches() {
assert_tripcode_eq!("50D13FhHVb0y", "POVD@psDFdsopfij");
assert_tripcode_eq!("/ybNw16ve2hX", "123ABCdef#=?");
}
#[test]
fn mona_invalid() {
assert_tripcode_eq!("???", "$23456789012");
assert_tripcode_eq!("???", "#abcdefghijklmnop");
assert_tripcode_eq!("???", "#fedcba9876543210!!");
assert_tripcode_eq!("???", "#abcdef0123456789ghi");
}
#[test]
fn sc_matches() {
let (t, k) = ("テスト!ケマワャエ・ァホヨイホ", "$。1008343131");
let k = SJIS.encode(k, EncoderTrap::Strict).unwrap();
assert_eq!(t, &ScSjis::generate(&k));
assert_tripcode_eq!("h3Si!7m4Qie8e.u", "$0123456789a", Sc);
}
#[test]
fn html_escaping() {
assert_tripcode_eq!("TIWS518hyaVm", "abc"def");
assert_tripcode_eq!("TIWS518hyaVm", "abc\"def");
assert_tripcode_eq!("pxr6zSrasrOD", "ab"cdef");
assert_tripcode_eq!("pxr6zSrasrOD", "ab\"cdef");
assert_tripcode_eq!("ZC2NVileD3Mz", "a"bcdef");
assert_tripcode_eq!("ZC2NVileD3Mz", "a\"bcdef");
assert_tripcode_eq!("Wg38i4X473pB", ""abcdef");
assert_tripcode_eq!("Wg38i4X473pB", "\"abcdef");
assert_tripcode_eq!("Gw/f5wZwNg", "<>");
assert_tripcode_eq!("Gw/f5wZwNg", "<>");
assert_tripcode_eq!("LZ4ugyvTWU", ""<");
assert_tripcode_eq!("LZ4ugyvTWU", "\"<");
assert_tripcode_eq!("MhCJJ7GVT.", "&");
assert_tripcode_eq!("MhCJJ7GVT.", "&", Fourchan);
assert_tripcode_eq!("2r2Ga7GHRc", "&");
}
#[test]
fn append() {
let mut tripcode = String::new();
Mona10::append(&"a", &mut tripcode);
Mona10::append(&"b", &mut tripcode);
Mona10::append(&"c", &mut tripcode);
Mona12::append(&"0123456789ab", &mut tripcode);
Mona::append(&"##0123456789", &mut tripcode);
let k = SJIS.encode(&"$。1008343131", EncoderTrap::Strict).unwrap();
ScKatakana::append(&k, &mut tripcode);
Sc15::append(&"$a9876543210", &mut tripcode);
assert_eq!(
"ZnBI2EKkq.\
taAZ7oPCCM\
wG1CV58ydQ\
Ly0gXVRR0yVs\
???\
テスト!ケマワャエ・ァホヨイホ\
x.r.XzgFZywTJhG\
",
&tripcode
);
}
}