qair 0.7.0

Send/receive files with builtin HTTP server and terminal QR code.
Documentation
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! HTTP handler to send files stored on disk with continuation support.
//!
//! Supports HTTP range, but not multi-range.

extern crate hyper;
extern crate iron;
extern crate mount;
extern crate percent_encoding;
extern crate router;

use self::hyper::header::ByteRangeSpec::*;
use self::hyper::mime::{Mime, SubLevel, TopLevel};
use super::not_shared;
use iron::middleware::Handler;
use iron::request::Request;
use iron::response::Response;
use iron::{headers, status, IronResult};
use std::fs::File;
use std::io::Seek;
use std::path::PathBuf;

/// Handler for HTTP requests that serves a file stored on disk.
pub struct ServeFile {
    path: PathBuf,
}

impl ServeFile {
    /// Creates a new handler that only serves the given file (regardless of
    /// which file was asked for)
    pub fn new(path: PathBuf) -> Self {
        ServeFile { path }
    }

    /// Iron handler for serving the given file starting at byte `requested_from`
    fn handle_for_range(
        &self,
        mut file: File,
        requested_from: u64,
        filesize: u64,
    ) -> IronResult<Response> {
        let error416 = Ok(Response::with((
            status::RangeNotSatisfiable,
            "416 Range Not Satisfiable",
        )));

        if filesize <= requested_from {
            return error416;
        }
        let seekret = file.seek(std::io::SeekFrom::Start(requested_from));
        match seekret {
            Ok(seekretlength) if seekretlength == requested_from => {
                let mut response = Response::with((status::PartialContent, file));
                let range = headers::ContentRangeSpec::Bytes {
                    range: Some((requested_from, filesize - 1)),
                    instance_length: None,
                };
                response.headers.set(headers::ContentRange(range));
                apply_default_send_file_headers(&mut response.headers);
                Ok(response)
            }
            _ => error416,
        }
    }
}

/// Returns the start position requested
///
/// HTTP supports retrieving part of a file, which is useful for continuing
/// an interrupted download.
///
/// This function returns `Some(n)` if the given HTTP client headers
/// ask to the file starting from byte `n` where 0 is the first byte.
/// It returns `None` if HTTP range is not requested or asked in
/// an unsupported form (which is rare).
fn parse_range(headers: &headers::Headers) -> Option<u64> {
    match headers.get::<headers::Range>() {
        Some(headers::Range::Bytes(ranges)) => {
            if let [AllFrom(from)] = ranges.as_slice() {
                Some(*from)
            } else {
                None
            }
        }
        _ => None,
    }
}

/// Add/set the default headers (for sending a file) to the given headers
fn apply_default_send_file_headers(headers: &mut headers::Headers) {
    let attachment_header = iron::headers::ContentDisposition {
        disposition: iron::headers::DispositionType::Attachment,
        parameters: Vec::new(),
    };
    headers.set(attachment_header);

    let content_type =
        headers::ContentType(Mime(TopLevel::Application, SubLevel::OctetStream, vec![]));
    headers.set(content_type);
}

impl Handler for ServeFile {
    /// Handler for HTTP requests that serves a file stored on disk.
    ///
    /// Supports continuing interrupted downloads (via HTTP Range).
    fn handle(&self, req: &mut Request) -> IronResult<Response> {
        let error = not_shared::not_shared_handler(req);

        let metadata = std::fs::metadata(&self.path);
        let filesize = match metadata {
            Ok(metadata) => metadata.len(),
            Err(_) => {
                // File open succeeded but looking at its size failed.
                // Could be a lot of things; let's keep error handling simple for now.
                return error;
            }
        };

        let file = match File::open(&self.path) {
            Ok(file) => file,
            Err(_) => {
                return error;
            }
        };

        match parse_range(&req.headers) {
            Some(from) => self.handle_for_range(file, from, filesize),
            None => {
                let mut response = Response::with((status::Ok, file));
                apply_default_send_file_headers(&mut response.headers);
                response.headers.set(headers::ContentLength(filesize));
                Ok(response)
            }
        }
    }
}