#[macro_use] extern crate error_chain;
extern crate reqwest;
extern crate crypto;
extern crate regex;
extern crate base64;
pub mod error;
use error::*;
use std::io::Read;
use std::ops::Deref;
use std::fs::File;
use std::str;
use reqwest::Client;
use reqwest::header::{Connection, UserAgent};
use crypto::{buffer, aes, blockmodes };
use crypto::buffer::{ ReadBuffer, WriteBuffer };
use regex::Regex;
#[derive(Debug, Clone)]
pub struct DlcLink {
pub url: String,
pub name: String,
pub size: String
}
impl DlcLink {
fn new() -> DlcLink {
DlcLink {
url: String::new(),
name: String::new(),
size: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct DlcPackage {
pub name: String,
pub password: String,
pub files: Vec<DlcLink>,
}
impl DlcPackage {
fn new() -> DlcPackage {
DlcPackage {
name: String::new(),
password: String::new(),
files: Vec::new(),
}
}
}
#[derive(Debug)]
pub struct DlcDecoder {
jd_app_name: String,
jd_decryption_key: Vec<u8>,
jd_decryption_iv: Vec<u8>
}
impl DlcDecoder {
pub fn new() -> DlcDecoder {
DlcDecoder {
jd_app_name: "pylo".to_string(),
jd_decryption_key: b"cb99b5cbc24db398".to_vec(),
jd_decryption_iv: b"9bc24cb995cb8db3".to_vec()
}
}
pub fn set_credentials<S, D>(&mut self, name: S, key: D, iv: D)
where S: Into<String>, D: Into<Vec<u8>> {
self.jd_app_name = name.into();
self.jd_decryption_key = key.into();
self.jd_decryption_iv = iv.into();
}
pub fn from_file<P: Into<String>>(&self, path: P) -> Result<DlcPackage> {
let mut file = File::open(path.into())?;
let mut data = Vec::new();
file.read_to_end(&mut data)?;
self.from_data(&data)
}
pub fn from_data(&self, data: &[u8]) -> Result<DlcPackage> {
let data = self.decrypt_dlc(data)?;
let mut dlc = self.parse_header(&data)?;
self.parse_body(&mut dlc, &data)?;
Ok(dlc)
}
fn decrypt_dlc(&self, data: &[u8]) -> Result<String> {
if data.len() <= 88 {
bail!("Corrupted data");
};
let (data, key) = data.split_at(data.len() - 88);
let key = self.get_jd_decryption_key(key)?;
let key = DlcDecoder::decrypt_raw_data(&key, &self.jd_decryption_key, &self.jd_decryption_iv)?;
let data = base64::decode(data)?;
let data = DlcDecoder::decrypt_raw_data(&data, key.deref(), key.deref())?;
let data = base64::decode(&data)?;
let data = String::from_utf8(data)?;
return Ok(data);
}
fn decrypt_raw_data(data: &[u8], key: &[u8], iv: &[u8]) -> Result<Vec<u8>> {
let mut decryptor = aes_cbc_decryptor(
aes::KeySize::KeySize128,
key,
iv,
blockmodes::NoPadding,
);
let mut buffer = [0; 4096];
let mut read_buffer = buffer::RefReadBuffer::new(data);
let mut writ_buffer = buffer::RefWriteBuffer::new(&mut buffer);
let mut result = Vec::new();
loop {
if decryptor.decrypt(&mut read_buffer, &mut writ_buffer, true).is_err() {
bail!("Can't decrypt");
}
if writ_buffer.is_empty() {
break;
}
result.extend_from_slice(writ_buffer.take_read_buffer().take_remaining());
};
result.retain(|x| *x != 0 as u8);
return Ok(result);
}
fn get_jd_decryption_key(&self, key: &[u8]) -> Result<Vec<u8>> {
let url = format!("http://service.jdownloader.org/dlcrypt/service.php?srcType=dlc&destType={}&data={}", &self.jd_app_name, str::from_utf8(key)?);
let mut res = Client::new().get(&url)
.header(Connection::close())
.header(UserAgent::new("Mozilla/5.3 (Windows; U; Windows NT 5.1; de; rv:1.8.1.6) Gecko/2232 Firefox/3.0.0.R"))
.send()?;
let mut key = Vec::new();
res.read_to_end(&mut key)?;
if key.len() != 33 {
bail!("Unexpected Error");
};
let key = base64::decode(&key[4..28])?;
Ok(key)
}
fn parse_header(&self, data: &str) -> Result<DlcPackage> {
let mut dlc = DlcPackage::new();
let re = Regex::new(r#"<package ([^>]*)"#)?;
let pck = re.find(&data).ok_or("Can't find package in data")?.as_str();
let re = Regex::new(r#"name="([^"]*)"#)?;
let t = re.find(&pck).ok_or("Can't find name in data")?;
dlc.name = String::from_utf8(base64::decode(&pck[t.start()+6..t.end()])?)?;
let re = Regex::new(r#"passwords="([^"]*)"#)?;
if let Some(t) = re.find(&pck) {
dlc.password = String::from_utf8(base64::decode(&pck[t.start()+11..t.end()])?)?;
}
Ok(dlc)
}
fn parse_body(&self, dlc: &mut DlcPackage, data: &str) -> Result<()> {
let mut files_xml: Vec<&str> = data.split("<file>").collect();
files_xml.remove(0);
for f in files_xml {
let details: Vec<&str> = f.split("<").collect();
let mut link = DlcLink::new();
for d in details {
if d.len() > 3 && d[..3] == "url".to_string() {
let buf = self.file_details(d.to_string(), 4);
link.url = buf;
} else if d.len() > 8 && d[..8] == "filename".to_string() {
let buf = self.file_details(d.to_string(), 9);
link.name = buf;
} else if d.len() > 4 && d[..4] == "size".to_string() {
let buf = self.file_details(d.to_string(), 5);
link.size = buf;
}
}
dlc.files.push(link);
}
Ok(())
}
fn file_details(&self, data: String, pos: usize) -> String {
let buf: String = match base64::decode(&data[pos..]) {
Ok(x) => {
match String::from_utf8(x) {
Ok(x) => x,
Err(_) => data[pos..].to_string(),
}
},
Err(_) => data[pos..].to_string(),
};
buf
}
}
use crypto::aessafe;
use crypto::blockmodes::{CbcDecryptor, PaddingProcessor};
use crypto::symmetriccipher::Decryptor;
use crypto::aes::KeySize;
fn aes_cbc_decryptor<X: PaddingProcessor + Send + 'static>(
key_size: KeySize,
key: &[u8],
iv: &[u8],
padding: X) -> Box<Decryptor + 'static> {
match key_size {
KeySize::KeySize128 => {
let aes_dec = aessafe::AesSafe128Decryptor::new(key);
let dec = Box::new(CbcDecryptor::new(aes_dec, padding, iv.to_vec()));
dec as Box<Decryptor + 'static>
}
KeySize::KeySize192 => {
let aes_dec = aessafe::AesSafe192Decryptor::new(key);
let dec = Box::new(CbcDecryptor::new(aes_dec, padding, iv.to_vec()));
dec as Box<Decryptor + 'static>
}
KeySize::KeySize256 => {
let aes_dec = aessafe::AesSafe256Decryptor::new(key);
let dec = Box::new(CbcDecryptor::new(aes_dec, padding, iv.to_vec()));
dec as Box<Decryptor + 'static>
}
}
}