1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2025 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::{CookieAttribute, CookieAttributeName, CookiePath};
19
use crate::combinator::optional;
20
use crate::parser::primitives::{literal, try_literal, zero_or_more_spaces};
21
use crate::parser::{string, ParseError, ParseErrorKind, ParseResult};
22
use crate::reader::Reader;
23

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

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

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

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

            
69
#[cfg(test)]
70
mod tests {
71
    use super::*;
72
    use crate::ast::{
73
        Expr, ExprKind, Placeholder, SourceInfo, Template, TemplateElement, Variable, Whitespace,
74
    };
75
    use crate::reader::Pos;
76
    use crate::typing::ToSource;
77

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

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

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

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

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

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

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

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

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

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