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
use std::fmt;
use std::io::{Read, Write, Result};
use std::collections::HashMap;

#[macro_use] extern crate lazy_static;
#[macro_use] extern crate failure;

// The main specification for this protocol is https://iterm2.com/documentation-images.html

pub mod dimension;
pub use dimension::Dimension;

#[derive(Clone)]
struct ArgumentMap(HashMap<&'static str, String>);

impl ArgumentMap {
    fn new() -> ArgumentMap {
        ArgumentMap(HashMap::new())
    }

    fn insert(&mut self, key: &'static str, value: impl Into<String>) {
        self.0.insert(key, value.into());
    }

    fn from_action(action: &TransferAction) -> ArgumentMap {
        let mut args = ArgumentMap::new();

        match action {
            TransferAction::Download => {
                args.insert("inline", "0");
            },

            TransferAction::Display { height, width, preserve_aspect_ratio } => {
                if let Some(width) = width {
                   args.insert("width", *width);
                }

                if let Some(height) = height {
                    args.insert("height", *height);
                }

                if let Some(preserve_aspect_ratio) = *preserve_aspect_ratio {
                    args.insert("preserveAspectRatio", if preserve_aspect_ratio {
                            "1"
                        } else {
                            "0"
                        }
                    );
                }

                args.insert("inline", "1");
            }
        }

        args
    }
}

impl fmt::Display for ArgumentMap {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        self.0
            .iter()
            .map(|(name, value)| write!(f, "{}={};", name, value))
            .collect()
    }
}

#[derive(Clone)]
pub enum TransferAction {
    Download,
    Display {
        width: Option<Dimension>,
        height: Option<Dimension>,
        preserve_aspect_ratio: Option<bool>
    }
}

pub struct FileTransfer<'a> {
    action: &'a TransferAction,
    data: Box<dyn Read>,
    name: Option<String>
}

impl<'a> FileTransfer<'a> {
    pub fn new(action: &'a TransferAction, data: Box<dyn Read>, name: Option<String>) -> FileTransfer {
        FileTransfer { action, data, name }
    }

    pub fn transfer(mut self, output: &mut dyn Write) -> Result<()> {
        let mut args: ArgumentMap = ArgumentMap::from_action(self.action);

        // I'm suprised this passed the lifetime checker
        let encoded_name;
        if let Some(name) = self.name {
            encoded_name = base64::encode(&name);
            args.insert("name", &encoded_name);
        }

        let mut contents = Vec::new();
        let size = self.data.read_to_end(&mut contents)?;
        let size_string = size.to_string();
        args.insert("size", &size_string);
    
        // TODO: Switch to a streaming base64 implementation
        write!(output, "\x1b]1337;File={}:{}\x07", args, base64::encode(&contents))
    }
}