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 crate::ast::*;
19
use crate::parser::error::*;
20
use crate::parser::primitives::try_literal;
21
use crate::parser::{string, ParseResult};
22
use crate::reader::Reader;
23

            
24
use super::placeholder;
25

            
26
/// Parse a filename with an optional password
27
///
28
/// This is very similar to the filename parser.
29
/// There is still a small different due to the password delimiter ":"
30
/// If the ':' character is part of the filename, it must be escaped.
31
/// While in the standard filename parser, you can not escape this character at all.
32
///
33
65
pub fn parse(reader: &mut Reader) -> ParseResult<Template> {
34
65
    let start = reader.cursor();
35
65

            
36
65
    let mut elements = vec![];
37
    loop {
38
140
        let start = reader.cursor();
39
140
        match placeholder::parse(reader) {
40
30
            Ok(placeholder) => {
41
30
                let element = TemplateElement::Placeholder(placeholder);
42
30
                elements.push(element);
43
            }
44
110
            Err(e) => {
45
110
                if e.recoverable {
46
110
                    let value = filename_password_content(reader)?;
47
110
                    if value.is_empty() {
48
65
                        break;
49
                    }
50
45
                    let encoded = reader.read_from(start.index);
51
45
                    let element = TemplateElement::String { value, encoded };
52
45
                    elements.push(element);
53
                } else {
54
                    return Err(e);
55
                }
56
            }
57
        }
58
    }
59
65
    if elements.is_empty() {
60
        let kind = ParseErrorKind::Filename;
61
        return Err(ParseError::new(start.pos, false, kind));
62
    }
63
65
    if let Some(TemplateElement::String { encoded, .. }) = elements.first() {
64
40
        if encoded.starts_with('[') {
65
            let kind = ParseErrorKind::Expecting {
66
                value: "filename".to_string(),
67
            };
68
            return Err(ParseError::new(start.pos, false, kind));
69
        }
70
    }
71

            
72
65
    let end = reader.cursor();
73
65
    Ok(Template {
74
65
        delimiter: None,
75
65
        elements,
76
65
        source_info: SourceInfo {
77
65
            start: start.pos,
78
65
            end: end.pos,
79
65
        },
80
65
    })
81
}
82

            
83
110
fn filename_password_content(reader: &mut Reader) -> ParseResult<String> {
84
110
    let mut s = String::new();
85
    loop {
86
155
        match filename_password_escaped_char(reader) {
87
            Ok(c) => {
88
                // ':' is escaped so that is it not recognized as the password delimiter
89
                // This is not due to Hurl format
90
                if c == ':' {
91
                    s.push('\\');
92
                }
93
                s.push(c);
94
            }
95
155
            Err(e) => {
96
155
                if e.recoverable {
97
155
                    let s2 = filename_password_text(reader);
98
155
                    if s2.is_empty() {
99
110
                        break;
100
45
                    } else {
101
45
                        s.push_str(&s2);
102
                    }
103
                } else {
104
                    return Err(e);
105
                }
106
            }
107
        }
108
    }
109
110
    Ok(s)
110
}
111

            
112
155
fn filename_password_text(reader: &mut Reader) -> String {
113
155
    let mut s = String::new();
114
    loop {
115
985
        let save = reader.cursor();
116
985
        match reader.read() {
117
            None => break,
118
985
            Some(c) => {
119
985
                if ['#', ';', '{', '}', ' ', '\n', '\\'].contains(&c) {
120
155
                    reader.seek(save);
121
155
                    break;
122
830
                } else {
123
830
                    s.push(c);
124
                }
125
            }
126
        }
127
    }
128
155
    s
129
}
130

            
131
155
fn filename_password_escaped_char(reader: &mut Reader) -> ParseResult<char> {
132
155
    try_literal("\\", reader)?;
133
    let start = reader.cursor();
134
    match reader.read() {
135
        Some('\\') => Ok('\\'),
136
        Some('b') => Ok('\x08'),
137
        Some('f') => Ok('\x0c'),
138
        Some('n') => Ok('\n'),
139
        Some('r') => Ok('\r'),
140
        Some('t') => Ok('\t'),
141
        Some('#') => Ok('#'),
142
        Some(';') => Ok(';'),
143
        Some(' ') => Ok(' '),
144
        Some('{') => Ok('{'),
145
        Some('}') => Ok('}'),
146
        Some(':') => Ok(':'),
147
        Some('u') => string::unicode(reader),
148
        _ => Err(ParseError::new(
149
            start.pos,
150
            false,
151
            ParseErrorKind::EscapeChar,
152
        )),
153
    }
154
}
155

            
156
#[cfg(test)]
157
mod tests {
158
    use super::*;
159
    use crate::reader::Pos;
160

            
161
    #[test]
162
    fn test_filename_with_password() {
163
        let mut reader = Reader::new("file\\:123:pwd\\:\\#:");
164
        assert_eq!(
165
            parse(&mut reader).unwrap(),
166
            Template {
167
                delimiter: None,
168
                elements: vec![TemplateElement::String {
169
                    value: "file\\:123:pwd\\:#:".to_string(),
170
                    encoded: "file\\:123:pwd\\:\\#:".to_string()
171
                }],
172
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 19)),
173
            }
174
        );
175
        assert_eq!(reader.cursor().index, 18);
176
    }
177

            
178
    #[test]
179
    fn test_parse() {
180
        let mut reader = Reader::new("/etc/client-cert.pem #foo");
181
        assert_eq!(
182
            parse(&mut reader).unwrap(),
183
            Template {
184
                delimiter: None,
185
                elements: vec![TemplateElement::String {
186
                    value: "/etc/client-cert.pem".to_string(),
187
                    encoded: "/etc/client-cert.pem".to_string()
188
                }],
189
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 21)),
190
            }
191
        );
192
        assert_eq!(reader.cursor().index, 20);
193
    }
194
}