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
//! An extension trait for HTTP requests to work with range requests

use crate::rangeext::RangeExt;
use ehttpd::{
    bytes::{Data, DataSliceExt, Source},
    error,
    error::Error,
    http::{Response, ResponseExt},
};
use std::{
    fs::File,
    io::{Read, Seek, SeekFrom},
    ops::{Range, RangeBounds, RangeInclusive},
};

/// An extension trait for HTTP responses to work with range requests
pub trait ResponseRangeExt
where
    Self: Sized,
{
    /// Creates a new `206 Partial Content` HTTP response
    fn new_206_partial_content() -> Self;

    /// Sets the `Accept-Ranges` to `bytes`
    fn set_accept_ranges_bytes(&mut self);
    /// Sets the `Accept-Ranges` to `none`
    fn set_accept_ranges_none(&mut self);

    /// Sets the `Content-Range` header
    fn set_content_range<T>(&mut self, range: T, total: u64) -> Result<(), Error>
    where
        T: RangeBounds<u64>;

    /// Sets the body for a `Partial Range` response
    ///
    /// # Note
    /// This function also sets the `Content-Length` and the `Content-Range` headers. Furthermore, it raises an error if
    /// `self.status` is not `206`
    fn set_body_data_range<T, R>(&mut self, data: T, range: R) -> Result<(), Error>
    where
        T: Into<Data>,
        R: RangeBounds<usize>;
    /// Sets the body for a `Partial Range` response
    ///
    /// # Note
    /// This function also sets the `Content-Length` and the `Content-Range` headers. Furthermore, it raises an error if
    /// `self.status` is not `206`
    fn set_body_file_range<T, R>(&mut self, file: T, range: R) -> Result<(), Error>
    where
        T: Into<File>,
        R: RangeBounds<u64>;
}
impl<const HEADER_SIZE_MAX: usize> ResponseRangeExt for Response<HEADER_SIZE_MAX> {
    fn new_206_partial_content() -> Self {
        Self::new_status_reason(206, "Partial Content")
    }

    fn set_accept_ranges_bytes(&mut self) {
        self.set_field("Accept-Ranges", "bytes")
    }
    fn set_accept_ranges_none(&mut self) {
        self.set_field("Accept-Ranges", "none")
    }

    fn set_content_range<T>(&mut self, range: T, total: u64) -> Result<(), Error>
    where
        T: RangeBounds<u64>,
    {
        // Compute the bounds
        let range = RangeInclusive::from_range_bounds(range, 0, total)
            .ok_or_else(|| error!("Range would exceed total limit"))?;
        let range_string = format!("{}-{}/{total}", range.start(), range.end());

        // Set the range
        self.set_field("Content-Range", range_string);
        Ok(())
    }

    fn set_body_data_range<T, R>(&mut self, data: T, range: R) -> Result<(), Error>
    where
        T: Into<Data>,
        R: RangeBounds<usize>,
    {
        // Ensure that we are a 206
        if !self.status.eq(b"206") {
            return Err(error!("Response is not a 206 response"));
        }

        // Prepare data and range
        let data: Data = data.into();
        let Range { start, end } =
            Range::from_range_bounds(range, 0, data.len()).ok_or_else(|| error!("Range would exceed data size"))?;
        let subdata = data.subcopy(start..end).expect("range would exceed data size");

        // Set content-range header and body data
        self.set_content_range(start as u64..end as u64, data.len() as u64)?;
        self.set_body_data(subdata);
        Ok(())
    }
    fn set_body_file_range<T, R>(&mut self, file: T, range: R) -> Result<(), Error>
    where
        T: Into<File>,
        R: RangeBounds<u64>,
    {
        // Ensure that we are a 206
        if !self.status.eq(b"206") {
            return Err(error!("Response is not a 206 response"));
        }

        // Open the file and get the file size
        let mut file = file.into();
        let file_size = file.metadata()?.len();
        let Range { start, end } =
            Range::from_range_bounds(range, 0, file_size).ok_or_else(|| error!("Range would exceed file size"))?;

        // Get the length and virtually truncate the file
        let len = end.saturating_sub(start);
        file.seek(SeekFrom::Start(start))?;
        let file = file.take(end.saturating_sub(start));

        // Set content-range and content-length header
        self.set_content_range(start..end, file_size)?;
        self.set_content_length(len);

        // Set the raw body
        let source = Source::from_other(file);
        self.set_body(source);
        Ok(())
    }
}