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, non_recover};
20
use crate::parser::duration::duration;
21
use crate::parser::error::*;
22
use crate::parser::number::{integer, number};
23
use crate::parser::primitives::*;
24
use crate::parser::string::*;
25
use crate::parser::{expr, filename, filename_password, ParseResult};
26
use crate::reader::Reader;
27
use crate::typing::Count;
28

            
29
/// Parse an option in an `[Options]` section.
30
4890
pub fn parse(reader: &mut Reader) -> ParseResult<EntryOption> {
31
4890
    let line_terminators = optional_line_terminators(reader)?;
32
4890
    let space0 = zero_or_more_spaces(reader)?;
33
4890
    let start = reader.cursor();
34
4890
    // We accept '_' even if there is no option name with this character. We do this to be able to
35
4890
    // enter the parsing of the option name and to have better error description (ex: 'max-redirs'
36
4890
    // vs 'max_redirs').
37
4890
    let option =
38
37513
        reader.read_while(|c| c.is_ascii_alphanumeric() || c == '-' || c == '.' || c == '_');
39
4890
    let space1 = zero_or_more_spaces(reader)?;
40
4890
    try_literal(":", reader)?;
41
3330
    let space2 = zero_or_more_spaces(reader)?;
42
3330
    let kind = match option.as_str() {
43
3330
        "aws-sigv4" => option_aws_sigv4(reader)?,
44
3290
        "cacert" => option_cacert(reader)?,
45
3230
        "cert" => option_cert(reader)?,
46
3165
        "compressed" => option_compressed(reader)?,
47
2755
        "connect-to" => option_connect_to(reader)?,
48
2690
        "delay" => option_delay(reader)?,
49
2595
        "insecure" => option_insecure(reader)?,
50
2495
        "http1.0" => option_http_10(reader)?,
51
2390
        "http1.1" => option_http_11(reader)?,
52
2315
        "http2" => option_http_2(reader)?,
53
2275
        "http3" => option_http_3(reader)?,
54
2245
        "ipv4" => option_ipv4(reader)?,
55
2215
        "ipv6" => option_ipv6(reader)?,
56
2185
        "key" => option_key(reader)?,
57
2135
        "location" => option_follow_location(reader)?,
58
1740
        "location-trusted" => option_follow_location_trusted(reader)?,
59
1690
        "max-redirs" => option_max_redirect(reader)?,
60
1585
        "netrc" => option_netrc(reader)?,
61
1555
        "netrc-file" => option_netrc_file(reader)?,
62
1520
        "netrc-optional" => option_netrc_optional(reader)?,
63
1490
        "output" => option_output(reader)?,
64
1360
        "path-as-is" => option_path_as_is(reader)?,
65
1325
        "proxy" => option_proxy(reader)?,
66
1255
        "repeat" => option_repeat(reader)?,
67
1175
        "resolve" => option_resolve(reader)?,
68
1125
        "retry" => option_retry(reader)?,
69
970
        "retry-interval" => option_retry_interval(reader)?,
70
860
        "skip" => option_skip(reader)?,
71
820
        "unix-socket" => option_unix_socket(reader)?,
72
790
        "user" => option_user(reader)?,
73
690
        "variable" => option_variable(reader)?,
74
155
        "verbose" => option_verbose(reader)?,
75
60
        "very-verbose" => option_very_verbose(reader)?,
76
        _ => {
77
10
            return Err(ParseError::new(
78
10
                start.pos,
79
10
                false,
80
10
                ParseErrorKind::InvalidOption(option.to_string()),
81
10
            ))
82
        }
83
    };
84

            
85
3295
    let line_terminator0 = line_terminator(reader)?;
86
3295
    Ok(EntryOption {
87
3295
        line_terminators,
88
3295
        space0,
89
3295
        space1,
90
3295
        space2,
91
3295
        kind,
92
3295
        line_terminator0,
93
3295
    })
94
}
95

            
96
40
fn option_aws_sigv4(reader: &mut Reader) -> ParseResult<OptionKind> {
97
40
    let value = unquoted_template(reader)?;
98
40
    Ok(OptionKind::AwsSigV4(value))
99
}
100

            
101
60
fn option_cacert(reader: &mut Reader) -> ParseResult<OptionKind> {
102
60
    let value = filename::parse(reader)?;
103
55
    Ok(OptionKind::CaCertificate(value))
104
}
105

            
106
65
fn option_cert(reader: &mut Reader) -> ParseResult<OptionKind> {
107
65
    let value = filename_password::parse(reader)?;
108
65
    Ok(OptionKind::ClientCert(value))
109
}
110

            
111
410
fn option_compressed(reader: &mut Reader) -> ParseResult<OptionKind> {
112
410
    let value = non_recover(boolean_option, reader)?;
113
410
    Ok(OptionKind::Compressed(value))
114
}
115

            
116
65
fn option_connect_to(reader: &mut Reader) -> ParseResult<OptionKind> {
117
65
    let value = unquoted_template(reader)?;
118
65
    Ok(OptionKind::ConnectTo(value))
119
}
120

            
121
95
fn option_delay(reader: &mut Reader) -> ParseResult<OptionKind> {
122
95
    let value = duration_option(reader)?;
123
90
    Ok(OptionKind::Delay(value))
124
}
125

            
126
395
fn option_follow_location(reader: &mut Reader) -> ParseResult<OptionKind> {
127
395
    let value = non_recover(boolean_option, reader)?;
128
395
    Ok(OptionKind::FollowLocation(value))
129
}
130

            
131
50
fn option_follow_location_trusted(reader: &mut Reader) -> ParseResult<OptionKind> {
132
50
    let value = non_recover(boolean_option, reader)?;
133
50
    Ok(OptionKind::FollowLocationTrusted(value))
134
}
135

            
136
105
fn option_http_10(reader: &mut Reader) -> ParseResult<OptionKind> {
137
105
    let value = non_recover(boolean_option, reader)?;
138
105
    Ok(OptionKind::Http10(value))
139
}
140

            
141
75
fn option_http_11(reader: &mut Reader) -> ParseResult<OptionKind> {
142
75
    let value = non_recover(boolean_option, reader)?;
143
75
    Ok(OptionKind::Http11(value))
144
}
145

            
146
40
fn option_http_2(reader: &mut Reader) -> ParseResult<OptionKind> {
147
40
    let value = non_recover(boolean_option, reader)?;
148
40
    Ok(OptionKind::Http2(value))
149
}
150

            
151
30
fn option_http_3(reader: &mut Reader) -> ParseResult<OptionKind> {
152
30
    let value = non_recover(boolean_option, reader)?;
153
30
    Ok(OptionKind::Http3(value))
154
}
155

            
156
100
fn option_insecure(reader: &mut Reader) -> ParseResult<OptionKind> {
157
100
    let value = non_recover(boolean_option, reader)?;
158
95
    Ok(OptionKind::Insecure(value))
159
}
160

            
161
30
fn option_ipv4(reader: &mut Reader) -> ParseResult<OptionKind> {
162
30
    let value = non_recover(boolean_option, reader)?;
163
30
    Ok(OptionKind::IpV4(value))
164
}
165

            
166
30
fn option_ipv6(reader: &mut Reader) -> ParseResult<OptionKind> {
167
30
    let value = non_recover(boolean_option, reader)?;
168
30
    Ok(OptionKind::IpV6(value))
169
}
170

            
171
50
fn option_key(reader: &mut Reader) -> ParseResult<OptionKind> {
172
50
    let value = filename::parse(reader)?;
173
50
    Ok(OptionKind::ClientKey(value))
174
}
175

            
176
105
fn option_max_redirect(reader: &mut Reader) -> ParseResult<OptionKind> {
177
105
    let value = non_recover(count_option, reader)?;
178
105
    Ok(OptionKind::MaxRedirect(value))
179
}
180

            
181
30
fn option_netrc(reader: &mut Reader) -> ParseResult<OptionKind> {
182
30
    let value = non_recover(boolean_option, reader)?;
183
30
    Ok(OptionKind::NetRc(value))
184
}
185

            
186
35
fn option_netrc_file(reader: &mut Reader) -> ParseResult<OptionKind> {
187
35
    let value = unquoted_template(reader)?;
188
35
    Ok(OptionKind::NetRcFile(value))
189
}
190

            
191
30
fn option_netrc_optional(reader: &mut Reader) -> ParseResult<OptionKind> {
192
30
    let value = non_recover(boolean_option, reader)?;
193
30
    Ok(OptionKind::NetRcOptional(value))
194
}
195

            
196
130
fn option_output(reader: &mut Reader) -> ParseResult<OptionKind> {
197
130
    let value = filename::parse(reader)?;
198
130
    Ok(OptionKind::Output(value))
199
}
200

            
201
35
fn option_path_as_is(reader: &mut Reader) -> ParseResult<OptionKind> {
202
35
    let value = non_recover(boolean_option, reader)?;
203
35
    Ok(OptionKind::PathAsIs(value))
204
}
205

            
206
70
fn option_proxy(reader: &mut Reader) -> ParseResult<OptionKind> {
207
70
    let value = unquoted_template(reader)?;
208
70
    Ok(OptionKind::Proxy(value))
209
}
210

            
211
80
fn option_repeat(reader: &mut Reader) -> ParseResult<OptionKind> {
212
80
    let value = non_recover(count_option, reader)?;
213
80
    Ok(OptionKind::Repeat(value))
214
}
215

            
216
50
fn option_resolve(reader: &mut Reader) -> ParseResult<OptionKind> {
217
50
    let value = unquoted_template(reader)?;
218
50
    Ok(OptionKind::Resolve(value))
219
}
220

            
221
155
fn option_retry(reader: &mut Reader) -> ParseResult<OptionKind> {
222
155
    let value = non_recover(count_option, reader)?;
223
150
    Ok(OptionKind::Retry(value))
224
}
225

            
226
110
fn option_retry_interval(reader: &mut Reader) -> ParseResult<OptionKind> {
227
110
    let value = non_recover(duration_option, reader)?;
228
110
    Ok(OptionKind::RetryInterval(value))
229
}
230

            
231
40
fn option_skip(reader: &mut Reader) -> ParseResult<OptionKind> {
232
40
    let value = non_recover(boolean_option, reader)?;
233
40
    Ok(OptionKind::Skip(value))
234
}
235

            
236
100
fn option_user(reader: &mut Reader) -> ParseResult<OptionKind> {
237
100
    let value = unquoted_template(reader)?;
238
100
    Ok(OptionKind::User(value))
239
}
240

            
241
30
fn option_unix_socket(reader: &mut Reader) -> ParseResult<OptionKind> {
242
30
    let value = unquoted_template(reader)?;
243
30
    Ok(OptionKind::UnixSocket(value))
244
}
245

            
246
535
fn option_variable(reader: &mut Reader) -> ParseResult<OptionKind> {
247
535
    let value = variable_definition(reader)?;
248
530
    Ok(OptionKind::Variable(value))
249
}
250

            
251
95
fn option_verbose(reader: &mut Reader) -> ParseResult<OptionKind> {
252
95
    let value = non_recover(boolean_option, reader)?;
253
95
    Ok(OptionKind::Verbose(value))
254
}
255

            
256
50
fn option_very_verbose(reader: &mut Reader) -> ParseResult<OptionKind> {
257
50
    let value = non_recover(boolean_option, reader)?;
258
50
    Ok(OptionKind::VeryVerbose(value))
259
}
260

            
261
340
fn count(reader: &mut Reader) -> ParseResult<Count> {
262
340
    let start = reader.cursor();
263
340
    let value = non_recover(integer, reader)?;
264
240
    if value == -1 {
265
40
        Ok(Count::Infinite)
266
200
    } else if value >= 0 {
267
195
        Ok(Count::Finite(value as usize))
268
    } else {
269
5
        let kind = ParseErrorKind::Expecting {
270
5
            value: "Expecting a count value".to_string(),
271
5
        };
272
5
        Err(ParseError::new(start.pos, false, kind))
273
    }
274
}
275

            
276
1545
fn boolean_option(reader: &mut Reader) -> ParseResult<BooleanOption> {
277
1545
    let start = reader.cursor();
278
1545
    match boolean(reader) {
279
1260
        Ok(v) => Ok(BooleanOption::Literal(v)),
280
        Err(_) => {
281
285
            reader.seek(start);
282
286
            let exp = expr::parse(reader).map_err(|e| {
283
5
                let kind = ParseErrorKind::Expecting {
284
5
                    value: "true|false".to_string(),
285
5
                };
286
5
                ParseError::new(e.pos, false, kind)
287
286
            })?;
288
280
            Ok(BooleanOption::Expression(exp))
289
        }
290
    }
291
}
292

            
293
340
fn count_option(reader: &mut Reader) -> ParseResult<CountOption> {
294
340
    let start = reader.cursor();
295
340
    match count(reader) {
296
235
        Ok(v) => Ok(CountOption::Literal(v)),
297
        Err(_) => {
298
105
            reader.seek(start);
299
106
            let exp = expr::parse(reader).map_err(|e| {
300
5
                let kind = ParseErrorKind::Expecting {
301
5
                    value: "integer >= -1".to_string(),
302
5
                };
303
5
                ParseError::new(e.pos, false, kind)
304
106
            })?;
305
100
            Ok(CountOption::Expression(exp))
306
        }
307
    }
308
}
309

            
310
205
fn duration_option(reader: &mut Reader) -> ParseResult<DurationOption> {
311
205
    let start = reader.cursor();
312
205
    match duration(reader) {
313
165
        Ok(v) => Ok(DurationOption::Literal(v)),
314
40
        Err(e) => {
315
40
            if e.recoverable {
316
35
                reader.seek(start);
317
35
                let exp = expr::parse(reader).map_err(|e| {
318
                    let kind = ParseErrorKind::Expecting {
319
                        value: "integer".to_string(),
320
                    };
321
                    ParseError::new(e.pos, false, kind)
322
35
                })?;
323
35
                Ok(DurationOption::Expression(exp))
324
            } else {
325
5
                Err(e)
326
            }
327
        }
328
    }
329
}
330

            
331
535
fn variable_definition(reader: &mut Reader) -> ParseResult<VariableDefinition> {
332
535
    let name = variable_name(reader)?;
333
530
    let space0 = zero_or_more_spaces(reader)?;
334
530
    literal("=", reader)?;
335
530
    let space1 = zero_or_more_spaces(reader)?;
336
530
    let value = variable_value(reader)?;
337
530
    Ok(VariableDefinition {
338
530
        name,
339
530
        space0,
340
530
        space1,
341
530
        value,
342
530
    })
343
}
344

            
345
535
fn variable_name(reader: &mut Reader) -> ParseResult<String> {
346
535
    let start = reader.cursor();
347
3062
    let name = reader.read_while(|c| c.is_alphanumeric() || c == '_' || c == '-');
348
535
    if name.is_empty() {
349
        let kind = ParseErrorKind::Expecting {
350
            value: "variable name".to_string(),
351
        };
352
        return Err(ParseError::new(start.pos, false, kind));
353
535
    } else if is_variable_reserved(&name) {
354
5
        let kind = ParseErrorKind::Variable(format!(
355
5
            "conflicts with the {name} function, use a different name"
356
5
        ));
357
5
        return Err(ParseError::new(start.pos, false, kind));
358
    }
359
530
    Ok(name)
360
}
361

            
362
530
fn variable_value(reader: &mut Reader) -> ParseResult<VariableValue> {
363
530
    choice(
364
530
        &[
365
636
            |p1| match null(p1) {
366
15
                Ok(()) => Ok(VariableValue::Null),
367
515
                Err(e) => Err(e),
368
636
            },
369
633
            |p1| match boolean(p1) {
370
20
                Ok(value) => Ok(VariableValue::Bool(value)),
371
495
                Err(e) => Err(e),
372
633
            },
373
629
            |p1| match number(p1) {
374
195
                Ok(value) => Ok(VariableValue::Number(value)),
375
300
                Err(e) => Err(e),
376
629
            },
377
590
            |p1| match quoted_template(p1) {
378
                Ok(value) => Ok(VariableValue::String(value)),
379
300
                Err(e) => Err(e),
380
590
            },
381
590
            |p1| match unquoted_template(p1) {
382
300
                Ok(value) => Ok(VariableValue::String(value)),
383
                Err(e) => Err(e),
384
590
            },
385
530
        ],
386
530
        reader,
387
530
    )
388
530
    .map_err(|e| {
389
        let kind = ParseErrorKind::Expecting {
390
            value: "variable value".to_string(),
391
        };
392
        ParseError::new(e.pos, false, kind)
393
530
    })
394
}
395

            
396
#[cfg(test)]
397
mod tests {
398
    use super::*;
399
    use crate::reader::Pos;
400

            
401
    #[test]
402
    fn test_option_insecure() {
403
        let mut reader = Reader::new("insecure: true");
404
        let option = parse(&mut reader).unwrap();
405
        assert_eq!(
406
            option,
407
            EntryOption {
408
                line_terminators: vec![],
409
                space0: Whitespace {
410
                    value: String::new(),
411
                    source_info: SourceInfo {
412
                        start: Pos { line: 1, column: 1 },
413
                        end: Pos { line: 1, column: 1 },
414
                    },
415
                },
416
                space1: Whitespace {
417
                    value: String::new(),
418
                    source_info: SourceInfo {
419
                        start: Pos { line: 1, column: 9 },
420
                        end: Pos { line: 1, column: 9 },
421
                    },
422
                },
423
                space2: Whitespace {
424
                    value: " ".to_string(),
425
                    source_info: SourceInfo {
426
                        start: Pos {
427
                            line: 1,
428
                            column: 10,
429
                        },
430
                        end: Pos {
431
                            line: 1,
432
                            column: 11,
433
                        },
434
                    },
435
                },
436
                kind: OptionKind::Insecure(BooleanOption::Literal(true)),
437
                line_terminator0: LineTerminator {
438
                    space0: Whitespace {
439
                        value: String::new(),
440
                        source_info: SourceInfo {
441
                            start: Pos {
442
                                line: 1,
443
                                column: 15,
444
                            },
445
                            end: Pos {
446
                                line: 1,
447
                                column: 15,
448
                            },
449
                        },
450
                    },
451
                    comment: None,
452
                    newline: Whitespace {
453
                        value: String::new(),
454
                        source_info: SourceInfo {
455
                            start: Pos {
456
                                line: 1,
457
                                column: 15,
458
                            },
459
                            end: Pos {
460
                                line: 1,
461
                                column: 15,
462
                            },
463
                        },
464
                    },
465
                },
466
            }
467
        );
468
    }
469

            
470
    #[test]
471
    fn test_option_insecure_error() {
472
        let mut reader = Reader::new("insecure: error");
473
        let error = parse(&mut reader).err().unwrap();
474
        assert!(!error.recoverable);
475
    }
476

            
477
    #[test]
478
    fn test_option_cacert() {
479
        let mut reader = Reader::new("cacert: /home/foo/cert.pem");
480
        let option = parse(&mut reader).unwrap();
481
        assert_eq!(
482
            option,
483
            EntryOption {
484
                line_terminators: vec![],
485
                space0: Whitespace {
486
                    value: String::new(),
487
                    source_info: SourceInfo {
488
                        start: Pos { line: 1, column: 1 },
489
                        end: Pos { line: 1, column: 1 },
490
                    },
491
                },
492
                space1: Whitespace {
493
                    value: String::new(),
494
                    source_info: SourceInfo {
495
                        start: Pos { line: 1, column: 7 },
496
                        end: Pos { line: 1, column: 7 },
497
                    },
498
                },
499
                space2: Whitespace {
500
                    value: " ".to_string(),
501
                    source_info: SourceInfo {
502
                        start: Pos { line: 1, column: 8 },
503
                        end: Pos { line: 1, column: 9 },
504
                    },
505
                },
506
                kind: OptionKind::CaCertificate(Template {
507
                    delimiter: None,
508
                    elements: vec![TemplateElement::String {
509
                        value: "/home/foo/cert.pem".to_string(),
510
                        encoded: "/home/foo/cert.pem".to_string()
511
                    }],
512
                    source_info: SourceInfo {
513
                        start: Pos { line: 1, column: 9 },
514
                        end: Pos {
515
                            line: 1,
516
                            column: 27,
517
                        },
518
                    },
519
                }),
520
                line_terminator0: LineTerminator {
521
                    space0: Whitespace {
522
                        value: String::new(),
523
                        source_info: SourceInfo {
524
                            start: Pos {
525
                                line: 1,
526
                                column: 27,
527
                            },
528
                            end: Pos {
529
                                line: 1,
530
                                column: 27,
531
                            },
532
                        },
533
                    },
534
                    comment: None,
535
                    newline: Whitespace {
536
                        value: String::new(),
537
                        source_info: SourceInfo {
538
                            start: Pos {
539
                                line: 1,
540
                                column: 27,
541
                            },
542
                            end: Pos {
543
                                line: 1,
544
                                column: 27,
545
                            },
546
                        },
547
                    },
548
                },
549
            }
550
        );
551
    }
552

            
553
    #[test]
554
    fn test_option_cacert_error() {
555
        let mut reader = Reader::new("cacert: ###");
556
        let error = parse(&mut reader).err().unwrap();
557
        assert!(!error.recoverable);
558
    }
559

            
560
    #[test]
561
    fn test_option_retry_error() {
562
        let mut reader = Reader::new("retry: ###");
563
        let error = parse(&mut reader).err().unwrap();
564
        assert!(!error.recoverable);
565
        assert_eq!(error.pos, Pos { line: 1, column: 8 });
566
        assert_eq!(
567
            error.kind,
568
            ParseErrorKind::Expecting {
569
                value: "integer >= -1".to_string()
570
            }
571
        );
572
    }
573

            
574
    #[test]
575
    fn test_variable_definition() {
576
        let mut reader = Reader::new("a=1");
577
        assert_eq!(
578
            variable_definition(&mut reader).unwrap(),
579
            VariableDefinition {
580
                name: "a".to_string(),
581
                space0: Whitespace {
582
                    value: String::new(),
583
                    source_info: SourceInfo {
584
                        start: Pos { line: 1, column: 2 },
585
                        end: Pos { line: 1, column: 2 },
586
                    },
587
                },
588
                space1: Whitespace {
589
                    value: String::new(),
590
                    source_info: SourceInfo {
591
                        start: Pos { line: 1, column: 3 },
592
                        end: Pos { line: 1, column: 3 },
593
                    },
594
                },
595
                value: VariableValue::Number(Number::Integer(1)),
596
            }
597
        );
598
    }
599

            
600
    #[test]
601
    fn test_variable_value() {
602
        let mut reader = Reader::new("null");
603
        assert_eq!(variable_value(&mut reader).unwrap(), VariableValue::Null);
604

            
605
        let mut reader = Reader::new("true");
606
        assert_eq!(
607
            variable_value(&mut reader).unwrap(),
608
            VariableValue::Bool(true)
609
        );
610

            
611
        let mut reader = Reader::new("1");
612
        assert_eq!(
613
            variable_value(&mut reader).unwrap(),
614
            VariableValue::Number(Number::Integer(1))
615
        );
616

            
617
        let mut reader = Reader::new("toto");
618
        assert_eq!(
619
            variable_value(&mut reader).unwrap(),
620
            VariableValue::String(Template {
621
                delimiter: None,
622
                elements: vec![TemplateElement::String {
623
                    value: "toto".to_string(),
624
                    encoded: "toto".to_string(),
625
                }],
626
                source_info: SourceInfo {
627
                    start: Pos { line: 1, column: 1 },
628
                    end: Pos { line: 1, column: 5 },
629
                },
630
            })
631
        );
632
        let mut reader = Reader::new("\"123\"");
633
        assert_eq!(
634
            variable_value(&mut reader).unwrap(),
635
            VariableValue::String(Template {
636
                delimiter: Some('"'),
637
                elements: vec![TemplateElement::String {
638
                    value: "123".to_string(),
639
                    encoded: "123".to_string(),
640
                }],
641
                source_info: SourceInfo {
642
                    start: Pos { line: 1, column: 1 },
643
                    end: Pos { line: 1, column: 6 },
644
                },
645
            })
646
        );
647
    }
648
}