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::combinator::optional;
20
use crate::parser::error::*;
21
use crate::parser::primitives::*;
22
use crate::parser::string::*;
23
use crate::parser::ParseResult;
24
use crate::reader::Reader;
25

            
26
635
pub fn cookiepath(reader: &mut Reader) -> ParseResult<CookiePath> {
27
635
    let start = reader.cursor().pos;
28
635

            
29
635
    // We create a specialized reader for the templated, error and created structures are
30
635
    // relative tho the main reader.
31
4127
    let s = reader.read_while(|c| c != '[');
32
635
    let mut template_reader = Reader::with_pos(s.as_str(), start);
33
635
    let name = unquoted_template(&mut template_reader)?;
34
635
    let attribute = optional(cookiepath_attribute, reader)?;
35
630
    Ok(CookiePath { name, attribute })
36
}
37

            
38
635
fn cookiepath_attribute(reader: &mut Reader) -> ParseResult<CookieAttribute> {
39
635
    try_literal("[", reader)?;
40
545
    let space0 = zero_or_more_spaces(reader)?;
41
545
    let name = cookiepath_attribute_name(reader)?;
42
540
    let space1 = zero_or_more_spaces(reader)?;
43
540
    literal("]", reader)?;
44
540
    Ok(CookieAttribute {
45
540
        space0,
46
540
        name,
47
540
        space1,
48
540
    })
49
}
50

            
51
545
fn cookiepath_attribute_name(reader: &mut Reader) -> ParseResult<CookieAttributeName> {
52
545
    let start = reader.cursor().pos;
53
4109
    let s = reader.read_while(|c| c.is_alphabetic() || c == '-');
54
545
    match s.to_lowercase().as_str() {
55
545
        "value" => Ok(CookieAttributeName::Value(s)),
56
530
        "expires" => Ok(CookieAttributeName::Expires(s)),
57
430
        "max-age" => Ok(CookieAttributeName::MaxAge(s)),
58
365
        "domain" => Ok(CookieAttributeName::Domain(s)),
59
320
        "path" => Ok(CookieAttributeName::Path(s)),
60
255
        "secure" => Ok(CookieAttributeName::Secure(s)),
61
100
        "httponly" => Ok(CookieAttributeName::HttpOnly(s)),
62
40
        "samesite" => Ok(CookieAttributeName::SameSite(s)),
63
5
        _ => Err(ParseError::new(
64
5
            start,
65
5
            false,
66
5
            ParseErrorKind::InvalidCookieAttribute,
67
5
        )),
68
    }
69
}
70

            
71
#[cfg(test)]
72
mod tests {
73
    use super::*;
74
    use crate::ast::SourceInfo;
75
    use crate::reader::Pos;
76

            
77
    #[test]
78
    fn cookiepath_simple() {
79
        let mut reader = Reader::new("cookie1");
80
        assert_eq!(
81
            cookiepath(&mut reader).unwrap(),
82
            CookiePath {
83
                name: Template {
84
                    delimiter: None,
85
                    elements: vec![TemplateElement::String {
86
                        value: "cookie1".to_string(),
87
                        encoded: "cookie1".to_string(),
88
                    }],
89
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 8)),
90
                },
91
                attribute: None,
92
            }
93
        );
94
        assert_eq!(reader.cursor().index, 7);
95
    }
96

            
97
    #[test]
98
    fn cookiepath_with_attribute() {
99
        let mut reader = Reader::new("cookie1[Domain]");
100
        assert_eq!(
101
            cookiepath(&mut reader).unwrap(),
102
            CookiePath {
103
                name: Template {
104
                    delimiter: None,
105
                    elements: vec![TemplateElement::String {
106
                        value: "cookie1".to_string(),
107
                        encoded: "cookie1".to_string(),
108
                    }],
109
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 8)),
110
                },
111
                attribute: Some(CookieAttribute {
112
                    space0: Whitespace {
113
                        value: String::new(),
114
                        source_info: SourceInfo::new(Pos::new(1, 9), Pos::new(1, 9)),
115
                    },
116
                    name: CookieAttributeName::Domain("Domain".to_string()),
117
                    space1: Whitespace {
118
                        value: String::new(),
119
                        source_info: SourceInfo::new(Pos::new(1, 15), Pos::new(1, 15)),
120
                    },
121
                }),
122
            }
123
        );
124
        assert_eq!(reader.cursor().index, 15);
125
    }
126

            
127
    #[test]
128
    fn cookiepath_with_template() {
129
        let mut reader = Reader::new("{{name}}[Domain]");
130
        assert_eq!(
131
            cookiepath(&mut reader).unwrap(),
132
            CookiePath {
133
                name: Template {
134
                    delimiter: None,
135
                    elements: vec![TemplateElement::Placeholder(Placeholder {
136
                        space0: Whitespace {
137
                            value: String::new(),
138
                            source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 3)),
139
                        },
140
                        expr: Expr {
141
                            kind: ExprKind::Variable(Variable {
142
                                name: "name".to_string(),
143
                                source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 7)),
144
                            }),
145
                            source_info: SourceInfo::new(Pos::new(1, 3), Pos::new(1, 7)),
146
                        },
147
                        space1: Whitespace {
148
                            value: String::new(),
149
                            source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 7)),
150
                        },
151
                    })],
152
                    source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 9)),
153
                },
154
                attribute: Some(CookieAttribute {
155
                    space0: Whitespace {
156
                        value: String::new(),
157
                        source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(1, 10)),
158
                    },
159
                    name: CookieAttributeName::Domain("Domain".to_string()),
160
                    space1: Whitespace {
161
                        value: String::new(),
162
                        source_info: SourceInfo::new(Pos::new(1, 16), Pos::new(1, 16)),
163
                    },
164
                }),
165
            }
166
        );
167
        assert_eq!(reader.cursor().index, 16);
168
    }
169

            
170
    #[test]
171
    fn cookiepath_error() {
172
        let mut reader = Reader::new("cookie1[");
173
        let error = cookiepath(&mut reader).err().unwrap();
174
        assert_eq!(error.pos, Pos { line: 1, column: 9 });
175
        assert_eq!(error.kind, ParseErrorKind::InvalidCookieAttribute);
176
        assert!(!error.recoverable);
177

            
178
        let mut reader = Reader::new("cookie1[{{field]");
179
        let error = cookiepath(&mut reader).err().unwrap();
180
        assert_eq!(error.pos, Pos { line: 1, column: 9 });
181
        assert_eq!(error.kind, ParseErrorKind::InvalidCookieAttribute);
182
        assert!(!error.recoverable);
183

            
184
        // Check that errors are well reported with a buffer that have already read data.
185
        let mut reader = Reader::new("xxxx{{cookie[Domain]");
186
        _ = reader.read_while(|c| c == 'x');
187

            
188
        let error = cookiepath(&mut reader).err().unwrap();
189
        assert_eq!(
190
            error.pos,
191
            Pos {
192
                line: 1,
193
                column: 13
194
            }
195
        );
196
        assert_eq!(
197
            error.kind,
198
            ParseErrorKind::Expecting {
199
                value: "}}".to_string()
200
            }
201
        );
202
        assert!(!error.recoverable);
203
    }
204

            
205
    #[test]
206
    fn test_cookiepath_attribute_name() {
207
        let mut reader = Reader::new("Domain");
208
        assert_eq!(
209
            cookiepath_attribute_name(&mut reader).unwrap(),
210
            CookieAttributeName::Domain("Domain".to_string())
211
        );
212
        assert_eq!(reader.cursor().index, 6);
213

            
214
        let mut reader = Reader::new("domain");
215
        assert_eq!(
216
            cookiepath_attribute_name(&mut reader).unwrap(),
217
            CookieAttributeName::Domain("domain".to_string())
218
        );
219
        assert_eq!(reader.cursor().index, 6);
220

            
221
        let mut reader = Reader::new("unknown");
222
        let error = cookiepath_attribute_name(&mut reader).err().unwrap();
223
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
224
        assert_eq!(error.kind, ParseErrorKind::InvalidCookieAttribute);
225
    }
226
}