1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use std::collections::HashSet;
use std::io::BufRead;
use std::io::Read;
use std::io::Write;

use failure::bail;
use failure::ensure;
use failure::format_err;
use failure::Error;
use failure::ResultExt;

use crate::armour;
use crate::digestable::Digestable;
use crate::packets;
use crate::packets::Event;
use crate::packets::Packet;
use crate::packets::Signature;
use crate::packets::SignatureType;
use crate::HashAlg;

#[derive(Clone, Debug)]
pub struct Doc {
    pub body: Option<Body>,
    pub signatures: Vec<Signature>,
}

#[derive(Clone, Debug)]
pub struct Body {
    pub digest: Digestable,
    pub sig_type: SignatureType,
    pub header: Option<packets::PlainData>,
}

pub fn read_doc<R: BufRead, W: Write>(mut from: R, put_content: W) -> Result<Doc, Error> {
    let first_byte = {
        let head = from.fill_buf()?;
        ensure!(!head.is_empty(), "empty file");
        head[0]
    };

    match first_byte {
        b'-' => armour::read_armoured_doc(from, put_content),
        _ => read_binary_doc(from, put_content),
    }
}

fn read_binary_doc<R: Read, W: Write>(from: R, mut put_content: W) -> Result<Doc, Error> {
    let mut from = iowrap::Pos::new(from);
    let mut signatures = Vec::with_capacity(16);
    let mut body = None;
    let mut body_modes = HashSet::with_capacity(4);

    packets::parse_packets(&mut from, &mut |ev| {
        match ev {
            Event::Packet(Packet::Signature(sig)) => signatures.push(sig),
            Event::Packet(Packet::OnePassHelper(help)) => {
                body_modes.insert((help.signature_type, help.hash_type));
            }
            Event::Packet(Packet::IgnoredJunk) | Event::Packet(Packet::PubKey(_)) => (),
            Event::PlainData(header, from) => {
                if body.is_some() {
                    bail!("not supported: multiple plain data segments");
                }

                let (sig_type, hash_type) = *match body_modes.len() {
                    0 => bail!("no body mode hint provided before document"),
                    1 => body_modes.iter().next().unwrap(),
                    _ => bail!("unsupported: multiple body mode hints: {:?}", body_modes),
                };

                let mut digest = digestable_for(hash_type)
                    .ok_or_else(|| format_err!("unsupported hash type: {:?}", hash_type))?;

                use packets::SignatureType;
                match sig_type {
                    SignatureType::Binary => (),
                    other => bail!("unsupported signature type in binary doc: {:?}", other),
                };

                let mut buf = [0u8; 8 * 1024];

                loop {
                    let read = from.read(&mut buf)?;
                    if 0 == read {
                        break;
                    }
                    let buf = &buf[..read];
                    digest.process(buf);
                    put_content.write_all(buf)?;
                }

                body = Some(Body {
                    digest,
                    sig_type,
                    header: Some(header),
                });
            }
        }
        Ok(())
    })
    .with_context(|_| format_err!("parsing after at around {}", from.position()))?;

    Ok(Doc { body, signatures })
}

pub fn digestable_for(hash_type: HashAlg) -> Option<Digestable> {
    Some(match hash_type {
        HashAlg::Sha1 => Digestable::sha1(),
        HashAlg::Sha256 => Digestable::sha256(),
        HashAlg::Sha512 => Digestable::sha512(),
        _ => return None,
    })
}