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
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*!
# Raw Response for Rocket Framework

This crate provides a response struct used for responding raw data.

See `examples`.
*/

pub extern crate mime;
extern crate mime_guess;
extern crate percent_encoding;
extern crate rocket;
#[macro_use]
extern crate derivative;

use std::io::{self, Read, ErrorKind, Cursor};
use std::fs::{self, File};
use std::path::Path;

use mime::Mime;

use rocket::response::{Response, Responder, Result};
use rocket::request::Request;

/// The response struct used for responding raw data.
#[derive(Derivative)]
#[derivative(Debug)]
pub struct RawResponse<'a> {
    #[derivative(Debug = "ignore")]
    pub data: Box<Read + 'a>,
    pub file_name: String,
    pub content_type: Option<Mime>,
    pub content_length: Option<u64>,
}

impl<'a> Responder<'a> for RawResponse<'a> {
    fn respond_to(self, _: &Request) -> Result<'a> {
        let mut response = Response::build();

        if !self.file_name.is_empty() {
            response.raw_header("Content-Disposition", format!("inline; filename*=UTF-8''{}", percent_encoding::percent_encode(self.file_name.as_bytes(), percent_encoding::QUERY_ENCODE_SET)));
        }

        if let Some(content_type) = self.content_type {
            response.raw_header("Content-Type", content_type.to_string());
        }

        if let Some(content_length) = self.content_length {
            response.raw_header("Content-Length", content_length.to_string());
        }

        response.streamed_body(self.data);

        response.ok()
    }
}

impl<'a> RawResponse<'a> {
    /// Create a `RawResponse` instance from a path of a file.
    pub fn from_file<P: AsRef<Path>, S: Into<String>>(path: P, file_name: Option<S>, content_type: Option<Mime>) -> io::Result<RawResponse<'static>> {
        let path = path.as_ref();

        if !path.exists() {
            return Err(io::Error::from(ErrorKind::NotFound));
        }

        if !path.is_file() {
            return Err(io::Error::from(ErrorKind::InvalidInput));
        }

        let file_name = match file_name {
            Some(file_name) => {
                let file_name = file_name.into();
                file_name
            }
            None => {
                path.file_name().unwrap().to_str().unwrap().to_string()
            }
        };

        let file_size = match fs::metadata(&path) {
            Ok(metadata) => {
                Some(metadata.len())
            }
            Err(e) => return Err(e)
        };

        let content_type = match content_type {
            Some(content_type) => content_type,
            None => match path.extension() {
                Some(extension) => {
                    mime_guess::get_mime_type(extension.to_str().unwrap())
                }
                None => mime::APPLICATION_OCTET_STREAM
            }
        };

        let data = Box::from(File::open(&path)?);

        Ok(RawResponse {
            data,
            file_name,
            content_type: Some(content_type),
            content_length: file_size,
        })
    }

    /// Create a `RawResponse` instance from a Vec<u8>.
    pub fn from_vec<S: Into<String>>(vec: Vec<u8>, file_name: S, content_type: Option<Mime>) -> RawResponse<'static> {
        let file_name = file_name.into();

        let content_length = vec.len();

        RawResponse {
            data: Box::from(Cursor::new(vec)),
            file_name,
            content_type,
            content_length: Some(content_length as u64),
        }
    }

    /// Create a `RawResponse` instance from a reader.
    pub fn from_reader<R: Read + 'a, S: Into<String>>(reader: R, file_name: S, content_type: Option<Mime>, content_length: Option<u64>) -> RawResponse<'a> {
        let file_name = file_name.into();

        RawResponse {
            data: Box::from(reader),
            file_name,
            content_type,
            content_length,
        }
    }
}