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
//! 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!("bytes {}-{}/{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
        self.body = Source::from_other(file);
        Ok(())
    }
}