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
use std::collections::HashSet;
use std::io::BufRead;
use std::io::Read;
use std::io::Write;

use anyhow::anyhow;
use anyhow::bail;
use anyhow::ensure;
use anyhow::Context;
use anyhow::Error;

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(|| anyhow!("unsupported hash type: {:?}", hash_type))?;

                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(|| anyhow!("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,
    })
}