1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2024 Orange
4
 *
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * You may obtain a copy of the License at
8
 *
9
 *          http://www.apache.org/licenses/LICENSE-2.0
10
 *
11
 * Unless required by applicable law or agreed to in writing, software
12
 * distributed under the License is distributed on an "AS IS" BASIS,
13
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
 * See the License for the specific language governing permissions and
15
 * limitations under the License.
16
 *
17
 */
18
use std::fs::File;
19
use std::io::{ErrorKind, Read};
20
use std::path::{Path, PathBuf};
21
use std::{fmt, fs, io};
22

            
23
/// Represents the input of read operation: can be either a file or standard input.
24
#[derive(Clone, Debug, PartialEq, Eq)]
25
pub enum Input {
26
    /// Read from file.
27
    File(PathBuf),
28
    /// Read from standard input.
29
    Stdin,
30
}
31

            
32
impl fmt::Display for Input {
33
16085
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34
16085
        let output = match self {
35
16080
            Input::File(file) => file.to_string_lossy().to_string(),
36
5
            Input::Stdin => "-".to_string(),
37
        };
38
16085
        write!(f, "{output}")
39
    }
40
}
41

            
42
impl From<&Path> for Input {
43
2490
    fn from(value: &Path) -> Self {
44
2490
        Input::File(value.to_path_buf())
45
    }
46
}
47

            
48
impl From<PathBuf> for Input {
49
65
    fn from(value: PathBuf) -> Self {
50
65
        Input::File(value)
51
    }
52
}
53

            
54
impl Input {
55
    /// Creates a new input from a string filename.
56
305
    pub fn new(filename: &str) -> Self {
57
305
        if filename == "-" {
58
5
            Input::Stdin
59
        } else {
60
300
            Input::File(PathBuf::from(filename))
61
        }
62
    }
63

            
64
    /// Reads the content of this input to a string, removing any BOM.
65
2945
    pub fn read_to_string(&self) -> Result<String, io::Error> {
66
2945
        match self {
67
2935
            Input::File(path) => {
68
2935
                let mut f = File::open(path)?;
69
2935
                let metadata = fs::metadata(path).unwrap();
70
2935
                let mut buffer = vec![0; metadata.len() as usize];
71
2935
                f.read_exact(&mut buffer)?;
72
2935
                string_from_utf8(buffer)
73
            }
74
            Input::Stdin => {
75
10
                let mut contents = String::new();
76
10
                io::stdin().read_to_string(&mut contents)?;
77
10
                Ok(contents)
78
            }
79
        }
80
    }
81
}
82

            
83
2935
fn string_from_utf8(buffer: Vec<u8>) -> Result<String, io::Error> {
84
2935
    let mut buffer = buffer;
85
2935
    strip_bom(&mut buffer);
86
2938
    String::from_utf8(buffer).map_err(|e| io::Error::new(ErrorKind::InvalidData, e))
87
}
88

            
89
/// Remove BOM from the input bytes
90
2935
fn strip_bom(bytes: &mut Vec<u8>) {
91
2935
    if bytes.starts_with(&[0xefu8, 0xbb, 0xbf]) {
92
20
        bytes.drain(0..3);
93
    }
94
}
95

            
96
#[cfg(test)]
97
pub mod tests {
98
    use super::*;
99

            
100
    #[test]
101
    fn test_strip_bom() {
102
        let mut bytes = vec![];
103
        strip_bom(&mut bytes);
104
        assert!(bytes.is_empty());
105

            
106
        let mut bytes = vec![0xef, 0xbb, 0xbf, 0x68, 0x65, 0x6c, 0x6c, 0x6f];
107
        strip_bom(&mut bytes);
108
        assert_eq!(bytes, vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]);
109

            
110
        let mut bytes = vec![0x68, 0x65, 0x6c, 0x6c, 0x6f];
111
        strip_bom(&mut bytes);
112
        assert_eq!(bytes, vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]);
113
    }
114

            
115
    #[test]
116
    fn test_string_from_utf8_bom() {
117
        let mut bytes = vec![];
118
        strip_bom(&mut bytes);
119
        assert_eq!(string_from_utf8(vec![]).unwrap(), "");
120
        assert_eq!(
121
            string_from_utf8(vec![0xef, 0xbb, 0xbf, 0x68, 0x65, 0x6c, 0x6c, 0x6f]).unwrap(),
122
            "hello"
123
        );
124
        assert_eq!(
125
            string_from_utf8(vec![0x68, 0x65, 0x6c, 0x6c, 0x6f]).unwrap(),
126
            "hello"
127
        );
128
        let err = string_from_utf8(vec![0xef]).err().unwrap();
129
        assert_eq!(
130
            err.to_string(),
131
            "incomplete utf-8 byte sequence from index 0"
132
        );
133
    }
134
}