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::{SourceInfo, Template, TemplateElement};
19
use crate::parser::primitives::try_literal;
20
use crate::parser::{string, ParseError, ParseErrorKind, ParseResult};
21
use crate::reader::Reader;
22

            
23
use super::placeholder;
24

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

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

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

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

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

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

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

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

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