1
/*
2
 * Hurl (https://hurl.dev)
3
 * Copyright (C) 2026 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::reader::Reader;
21

            
22
use super::error::{ParseError, ParseErrorKind};
23
use super::primitives::{literal, try_literal, zero_or_more_spaces};
24
use super::{ParseResult, string};
25

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

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

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

            
51
500
fn cookiepath_attribute_name(reader: &mut Reader) -> ParseResult<CookieAttributeName> {
52
500
    let start = reader.cursor().pos;
53
3664
    let s = reader.read_while(|c| c.is_alphabetic() || c == '-');
54
500
    match s.to_lowercase().as_str() {
55
500
        "value" => Ok(CookieAttributeName::Value(s)),
56
488
        "expires" => Ok(CookieAttributeName::Expires(s)),
57
360
        "max-age" => Ok(CookieAttributeName::MaxAge(s)),
58
308
        "domain" => Ok(CookieAttributeName::Domain(s)),
59
272
        "path" => Ok(CookieAttributeName::Path(s)),
60
204
        "secure" => Ok(CookieAttributeName::Secure(s)),
61
80
        "httponly" => Ok(CookieAttributeName::HttpOnly(s)),
62
32
        "samesite" => Ok(CookieAttributeName::SameSite(s)),
63
4
        _ => Err(ParseError::new(
64
4
            start,
65
4
            false,
66
4
            ParseErrorKind::InvalidCookieAttribute,
67
4
        )),
68
    }
69
}
70

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

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

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

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

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

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

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

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

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

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

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