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::{choice, ParseError as ParseErrorTrait};
20
use crate::parser::cookiepath::cookiepath;
21
use crate::parser::primitives::*;
22
use crate::parser::string::*;
23
use crate::parser::{ParseError, ParseErrorKind, ParseResult};
24
use crate::reader::{Pos, Reader};
25

            
26
26480
pub fn query(reader: &mut Reader) -> ParseResult<Query> {
27
26480
    let start = reader.cursor();
28
26480
    let value = query_value(reader)?;
29
19305
    let end = reader.cursor();
30
19305
    Ok(Query {
31
19305
        source_info: SourceInfo::new(start.pos, end.pos),
32
19305
        value,
33
19305
    })
34
}
35

            
36
26480
fn query_value(reader: &mut Reader) -> ParseResult<QueryValue> {
37
26480
    choice(
38
26480
        &[
39
26480
            status_query,
40
26480
            url_query,
41
26480
            header_query,
42
26480
            cookie_query,
43
26480
            body_query,
44
26480
            xpath_query,
45
26480
            jsonpath_query,
46
26480
            regex_query,
47
26480
            variable_query,
48
26480
            duration_query,
49
26480
            bytes_query,
50
26480
            sha256_query,
51
26480
            md5_query,
52
26480
            certificate_query,
53
26480
        ],
54
26480
        reader,
55
26480
    )
56
}
57

            
58
26480
fn status_query(reader: &mut Reader) -> ParseResult<QueryValue> {
59
26480
    try_literal("status", reader)?;
60
105
    Ok(QueryValue::Status)
61
}
62

            
63
26375
fn url_query(reader: &mut Reader) -> ParseResult<QueryValue> {
64
26375
    try_literal("url", reader)?;
65
155
    Ok(QueryValue::Url)
66
}
67

            
68
26220
fn header_query(reader: &mut Reader) -> ParseResult<QueryValue> {
69
26220
    try_literal("header", reader)?;
70
1275
    let space0 = one_or_more_spaces(reader)?;
71
1275
    let name = quoted_template(reader).map_err(|e| e.to_non_recoverable())?;
72
1275
    Ok(QueryValue::Header { space0, name })
73
}
74

            
75
24945
fn cookie_query(reader: &mut Reader) -> ParseResult<QueryValue> {
76
24945
    try_literal("cookie", reader)?;
77
550
    let space0 = one_or_more_spaces(reader)?;
78

            
79
    // Read the whole value of the coookie path and parse it with a specialized reader.
80
550
    let start = reader.cursor();
81
550
    let s = quoted_oneline_string(reader)?;
82
    // todo should work with an encodedString in order to support escape sequence
83
    // or decode escape sequence with the cookiepath parser
84

            
85
    // We will parse the cookiepath value without `"`.
86
545
    let pos = Pos::new(start.pos.line, start.pos.column + 1);
87
545
    let mut cookiepath_reader = Reader::with_pos(s.as_str(), pos);
88
545
    let expr = cookiepath(&mut cookiepath_reader)?;
89

            
90
540
    Ok(QueryValue::Cookie { space0, expr })
91
}
92

            
93
24395
fn body_query(reader: &mut Reader) -> ParseResult<QueryValue> {
94
24395
    try_literal("body", reader)?;
95
6050
    Ok(QueryValue::Body)
96
}
97

            
98
18345
fn xpath_query(reader: &mut Reader) -> ParseResult<QueryValue> {
99
18345
    try_literal("xpath", reader)?;
100
1420
    let space0 = one_or_more_spaces(reader)?;
101
1421
    let expr = quoted_template(reader).map_err(|e| e.to_non_recoverable())?;
102
1415
    Ok(QueryValue::Xpath { space0, expr })
103
}
104

            
105
16925
fn jsonpath_query(reader: &mut Reader) -> ParseResult<QueryValue> {
106
16925
    try_literal("jsonpath", reader)?;
107
7090
    let space0 = one_or_more_spaces(reader)?;
108
    //let expr = jsonpath_expr(reader)?;
109
    //  let start = reader.state.pos.clone();
110
7091
    let expr = quoted_template(reader).map_err(|e| e.to_non_recoverable())?;
111
    //    let end = reader.state.pos.clone();
112
    //    let expr = Template {
113
    //        elements: template.elements.iter().map(|e| match e {
114
    //            TemplateElement::String { value, encoded } => HurlTemplateElement::Literal {
115
    //                value: HurlString2 { value: value.clone(), encoded: Some(encoded.clone()) }
116
    //            },
117
    //            TemplateElement::Expression(value) => HurlTemplateElement::Expression { value: value.clone() }
118
    //        }).collect(),
119
    //        quotes: true,
120
    //        source_info: SourceInfo { start, end },
121
    //    };
122

            
123
7085
    Ok(QueryValue::Jsonpath { space0, expr })
124
}
125

            
126
9835
fn regex_query(reader: &mut Reader) -> ParseResult<QueryValue> {
127
9835
    try_literal("regex", reader)?;
128
120
    let space0 = one_or_more_spaces(reader)?;
129
120
    let value = regex_value(reader)?;
130
115
    Ok(QueryValue::Regex { space0, value })
131
}
132

            
133
450
pub fn regex_value(reader: &mut Reader) -> ParseResult<RegexValue> {
134
450
    choice(
135
450
        &[
136
540
            |p1| match quoted_template(p1) {
137
285
                Ok(value) => Ok(RegexValue::Template(value)),
138
165
                Err(e) => Err(e),
139
540
            },
140
483
            |p1| match regex(p1) {
141
160
                Ok(value) => Ok(RegexValue::Regex(value)),
142
5
                Err(e) => Err(e),
143
483
            },
144
450
        ],
145
450
        reader,
146
450
    )
147
451
    .map_err(|e| {
148
5
        let kind = ParseErrorKind::Expecting {
149
5
            value: "\" or /".to_string(),
150
5
        };
151
5
        ParseError::new(e.pos, false, kind)
152
451
    })
153
}
154

            
155
9715
fn variable_query(reader: &mut Reader) -> ParseResult<QueryValue> {
156
9715
    try_literal("variable", reader)?;
157
1065
    let space0 = one_or_more_spaces(reader)?;
158
1065
    let name = quoted_template(reader).map_err(|e| e.to_non_recoverable())?;
159
1065
    Ok(QueryValue::Variable { space0, name })
160
}
161

            
162
8650
fn duration_query(reader: &mut Reader) -> ParseResult<QueryValue> {
163
8650
    try_literal("duration", reader)?;
164
55
    Ok(QueryValue::Duration)
165
}
166

            
167
8595
fn bytes_query(reader: &mut Reader) -> ParseResult<QueryValue> {
168
8595
    try_literal("bytes", reader)?;
169
845
    Ok(QueryValue::Bytes)
170
}
171

            
172
7750
fn sha256_query(reader: &mut Reader) -> ParseResult<QueryValue> {
173
7750
    try_literal("sha256", reader)?;
174
200
    Ok(QueryValue::Sha256)
175
}
176

            
177
7550
fn md5_query(reader: &mut Reader) -> ParseResult<QueryValue> {
178
7550
    try_literal("md5", reader)?;
179
190
    Ok(QueryValue::Md5)
180
}
181

            
182
7360
fn certificate_query(reader: &mut Reader) -> ParseResult<QueryValue> {
183
7360
    try_literal("certificate", reader)?;
184
210
    let space0 = one_or_more_spaces(reader)?;
185
210
    let field = certificate_field(reader)?;
186
210
    Ok(QueryValue::Certificate {
187
210
        space0,
188
210
        attribute_name: field,
189
210
    })
190
}
191

            
192
210
fn certificate_field(reader: &mut Reader) -> ParseResult<CertificateAttributeName> {
193
210
    literal("\"", reader)?;
194
210
    if try_literal(r#"Subject""#, reader).is_ok() {
195
25
        Ok(CertificateAttributeName::Subject)
196
185
    } else if try_literal(r#"Issuer""#, reader).is_ok() {
197
25
        Ok(CertificateAttributeName::Issuer)
198
160
    } else if try_literal(r#"Start-Date""#, reader).is_ok() {
199
55
        Ok(CertificateAttributeName::StartDate)
200
105
    } else if try_literal(r#"Expire-Date""#, reader).is_ok() {
201
80
        Ok(CertificateAttributeName::ExpireDate)
202
25
    } else if try_literal(r#"Serial-Number""#, reader).is_ok() {
203
25
        Ok(CertificateAttributeName::SerialNumber)
204
    } else {
205
        let value =
206
            "Field <Subject>, <Issuer>, <Start-Date>, <Expire-Date> or <Serial-Number>".to_string();
207
        let kind = ParseErrorKind::Expecting { value };
208
        let cur = reader.cursor();
209
        Err(ParseError::new(cur.pos, false, kind))
210
    }
211
}
212

            
213
#[cfg(test)]
214
mod tests {
215
    use super::*;
216
    use crate::parser::filter::filters;
217
    use crate::reader::Pos;
218

            
219
    #[test]
220
    fn test_query() {
221
        let mut reader = Reader::new("status");
222
        assert_eq!(
223
            query(&mut reader).unwrap(),
224
            Query {
225
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 7)),
226
                value: QueryValue::Status,
227
            }
228
        );
229
    }
230

            
231
    #[test]
232
    fn test_status_query() {
233
        let mut reader = Reader::new("status");
234
        assert_eq!(
235
            query(&mut reader).unwrap(),
236
            Query {
237
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 7)),
238
                value: QueryValue::Status,
239
            }
240
        );
241
    }
242

            
243
    #[test]
244
    fn test_header_query() {
245
        let mut reader = Reader::new("header \"Foo\"");
246
        assert_eq!(
247
            header_query(&mut reader).unwrap(),
248
            QueryValue::Header {
249
                space0: Whitespace {
250
                    value: String::from(" "),
251
                    source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 8)),
252
                },
253
                name: Template {
254
                    delimiter: Some('"'),
255
                    elements: vec![TemplateElement::String {
256
                        value: "Foo".to_string(),
257
                        encoded: "Foo".to_string(),
258
                    }],
259
                    source_info: SourceInfo::new(Pos::new(1, 8), Pos::new(1, 13)),
260
                },
261
            }
262
        );
263
    }
264

            
265
    #[test]
266
    fn test_cookie_query() {
267
        let mut reader = Reader::new("cookie \"Foo[Domain]\"");
268
        assert_eq!(
269
            cookie_query(&mut reader).unwrap(),
270
            QueryValue::Cookie {
271
                space0: Whitespace {
272
                    value: String::from(" "),
273
                    source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 8)),
274
                },
275
                expr: CookiePath {
276
                    name: Template {
277
                        delimiter: None,
278
                        elements: vec![TemplateElement::String {
279
                            value: "Foo".to_string(),
280
                            encoded: "Foo".to_string(),
281
                        }],
282
                        source_info: SourceInfo::new(Pos::new(1, 9), Pos::new(1, 12)),
283
                    },
284
                    attribute: Some(CookieAttribute {
285
                        space0: Whitespace {
286
                            value: String::new(),
287
                            source_info: SourceInfo::new(Pos::new(1, 13), Pos::new(1, 13)),
288
                        },
289
                        name: CookieAttributeName::Domain("Domain".to_string()),
290
                        space1: Whitespace {
291
                            value: String::new(),
292
                            source_info: SourceInfo::new(Pos::new(1, 19), Pos::new(1, 19)),
293
                        },
294
                    }),
295
                },
296
            }
297
        );
298
        assert_eq!(reader.cursor().index, 20);
299

            
300
        // todo test with escape sequence
301
        //let mut reader = Reader::init("cookie \"cookie\u{31}\"");
302
    }
303

            
304
    #[test]
305
    fn test_xpath_query() {
306
        let mut reader = Reader::new("xpath \"normalize-space(//head/title)\"");
307
        assert_eq!(
308
            xpath_query(&mut reader).unwrap(),
309
            QueryValue::Xpath {
310
                space0: Whitespace {
311
                    value: String::from(" "),
312
                    source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 7)),
313
                },
314
                expr: Template {
315
                    delimiter: Some('"'),
316
                    elements: vec![TemplateElement::String {
317
                        value: String::from("normalize-space(//head/title)"),
318
                        encoded: String::from("normalize-space(//head/title)"),
319
                    }],
320
                    source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 38)),
321
                },
322
            },
323
        );
324

            
325
        let mut reader = Reader::new("xpath \"normalize-space(//div[contains(concat(' ',normalize-space(@class),' '),' monthly-price ')])\"");
326
        assert_eq!(xpath_query(&mut reader).unwrap(), QueryValue::Xpath {
327
            space0: Whitespace { value: String::from(" "), source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 7)) },
328
            expr: Template {
329
                delimiter: Some('"'),
330
                elements: vec![
331
                    TemplateElement::String {
332
                        value: String::from("normalize-space(//div[contains(concat(' ',normalize-space(@class),' '),' monthly-price ')])"),
333
                        encoded: String::from("normalize-space(//div[contains(concat(' ',normalize-space(@class),' '),' monthly-price ')])"),
334
                    }
335
                ],
336
                source_info: SourceInfo::new(Pos::new(1, 7), Pos::new(1, 100)),
337
            },
338

            
339
        });
340
    }
341

            
342
    #[test]
343
    fn test_jsonpath_query() {
344
        let mut reader = Reader::new("jsonpath \"$['statusCode']\"");
345
        assert_eq!(
346
            jsonpath_query(&mut reader).unwrap(),
347
            QueryValue::Jsonpath {
348
                space0: Whitespace {
349
                    value: String::from(" "),
350
                    source_info: SourceInfo::new(Pos::new(1, 9), Pos::new(1, 10)),
351
                },
352
                expr: Template {
353
                    elements: vec![TemplateElement::String {
354
                        value: "$['statusCode']".to_string(),
355
                        encoded: "$['statusCode']".to_string(),
356
                    }],
357
                    delimiter: Some('"'),
358
                    source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(1, 27)),
359
                },
360
            },
361
        );
362
        let mut reader = Reader::new("jsonpath \"$.success\"");
363
        assert_eq!(
364
            jsonpath_query(&mut reader).unwrap(),
365
            QueryValue::Jsonpath {
366
                space0: Whitespace {
367
                    value: String::from(" "),
368
                    source_info: SourceInfo::new(Pos::new(1, 9), Pos::new(1, 10)),
369
                },
370
                expr: Template {
371
                    elements: vec![TemplateElement::String {
372
                        value: "$.success".to_string(),
373
                        encoded: "$.success".to_string(),
374
                    }],
375
                    delimiter: Some('"'),
376
                    source_info: SourceInfo::new(Pos::new(1, 10), Pos::new(1, 21)),
377
                },
378
            },
379
        );
380
    }
381

            
382
    #[test]
383
    fn test_query_with_filters() {
384
        let mut reader = Reader::new("body urlDecode ");
385
        assert_eq!(
386
            query(&mut reader).unwrap(),
387
            Query {
388
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 5)),
389
                value: QueryValue::Body,
390
            }
391
        );
392
        assert_eq!(
393
            filters(&mut reader).unwrap(),
394
            vec![(
395
                Whitespace {
396
                    value: " ".to_string(),
397
                    source_info: SourceInfo::new(Pos::new(1, 5), Pos::new(1, 6))
398
                },
399
                Filter {
400
                    source_info: SourceInfo::new(Pos::new(1, 6), Pos::new(1, 15)),
401
                    value: FilterValue::UrlDecode,
402
                }
403
            )]
404
        );
405
        assert_eq!(reader.cursor().index, 14);
406
    }
407
}