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::VersionValue::VersionAny;
19
use crate::ast::{
20
    Body, Entry, HurlFile, Method, Request, Response, Section, SourceInfo, Status, StatusValue,
21
    Version, VersionValue,
22
};
23
use crate::combinator::{optional, zero_or_more};
24
use crate::parser::bytes::bytes;
25
use crate::parser::primitives::{
26
    eof, key_value, line_terminator, one_or_more_spaces, optional_line_terminators, try_literal,
27
    zero_or_more_spaces,
28
};
29
use crate::parser::sections::{request_sections, response_sections};
30
use crate::parser::string::unquoted_template;
31
use crate::parser::{ParseError, ParseErrorKind, ParseResult};
32
use crate::reader::Reader;
33

            
34
4345
pub fn hurl_file(reader: &mut Reader) -> ParseResult<HurlFile> {
35
4345
    let entries = zero_or_more(entry, reader)?;
36
4100
    let line_terminators = optional_line_terminators(reader)?;
37
4100
    eof(reader)?;
38
4100
    Ok(HurlFile {
39
4100
        entries,
40
4100
        line_terminators,
41
4100
    })
42
}
43

            
44
16600
fn entry(reader: &mut Reader) -> ParseResult<Entry> {
45
16600
    let req = request(reader)?;
46
15435
    let resp = optional(response, reader)?;
47
15360
    Ok(Entry {
48
15360
        request: req,
49
15360
        response: resp,
50
15360
    })
51
}
52

            
53
16600
fn request(reader: &mut Reader) -> ParseResult<Request> {
54
16600
    let start = reader.cursor();
55
16600
    let line_terminators = optional_line_terminators(reader)?;
56
16600
    let space0 = zero_or_more_spaces(reader)?;
57
16600
    let method = method(reader)?;
58
15585
    let space1 = one_or_more_spaces(reader)?;
59
15565
    let url = unquoted_template(reader)?;
60
15560
    let line_terminator0 = line_terminator(reader)?;
61
15560
    let headers = zero_or_more(key_value, reader)?;
62
15560
    let sections = request_sections(reader)?;
63
15505
    let body = optional(body, reader)?;
64
15440
    let source_info = SourceInfo::new(start.pos, reader.cursor().pos);
65

            
66
15440
    check_duplicated_sections(&sections)?;
67

            
68
15435
    Ok(Request {
69
15435
        line_terminators,
70
15435
        space0,
71
15435
        method,
72
15435
        space1,
73
15435
        url,
74
15435
        line_terminator0,
75
15435
        headers,
76
15435
        sections,
77
15435
        body,
78
15435
        source_info,
79
15435
    })
80
}
81

            
82
15435
fn response(reader: &mut Reader) -> ParseResult<Response> {
83
15435
    let start = reader.cursor();
84
15435
    let line_terminators = optional_line_terminators(reader)?;
85
15435
    let space0 = zero_or_more_spaces(reader)?;
86
15435
    let version = version(reader)?;
87
14070
    let space1 = one_or_more_spaces(reader)?;
88
14070
    let status = status(reader)?;
89
14065
    let line_terminator0 = line_terminator(reader)?;
90
14065
    let headers = zero_or_more(key_value, reader)?;
91
14065
    let sections = response_sections(reader)?;
92
14005
    let body = optional(body, reader)?;
93
14005
    let source_info = SourceInfo::new(start.pos, reader.cursor().pos);
94

            
95
14005
    check_duplicated_sections(&sections)?;
96

            
97
14000
    Ok(Response {
98
14000
        line_terminators,
99
14000
        space0,
100
14000
        version,
101
14000
        space1,
102
14000
        status,
103
14000
        line_terminator0,
104
14000
        headers,
105
14000
        sections,
106
14000
        body,
107
14000
        source_info,
108
14000
    })
109
}
110

            
111
29445
fn check_duplicated_sections(sections: &[Section]) -> Result<(), ParseError> {
112
29445
    let mut section_names = vec![];
113
29445
    for section in sections {
114
12225
        if section_names.contains(&section.identifier()) {
115
10
            return Err(ParseError::new(
116
10
                section.source_info.start,
117
10
                false,
118
10
                ParseErrorKind::DuplicateSection,
119
10
            ));
120
12215
        } else {
121
12215
            section_names.push(section.identifier());
122
        }
123
    }
124
29435
    Ok(())
125
}
126

            
127
16600
fn method(reader: &mut Reader) -> ParseResult<Method> {
128
16600
    if reader.is_eof() {
129
995
        let kind = ParseErrorKind::Method {
130
995
            name: "<EOF>".to_string(),
131
995
        };
132
995
        return Err(ParseError::new(reader.cursor().pos, true, kind));
133
    }
134
15605
    let start = reader.cursor();
135
68571
    let name = reader.read_while(|c| c.is_ascii_alphabetic());
136
15605
    if name.is_empty() || name.to_uppercase() != name {
137
20
        let kind = ParseErrorKind::Method { name };
138
20
        Err(ParseError::new(start.pos, false, kind))
139
    } else {
140
15585
        Ok(Method::new(&name))
141
    }
142
}
143

            
144
15435
fn version(reader: &mut Reader) -> ParseResult<Version> {
145
15435
    let start = reader.cursor();
146
15435
    try_literal("HTTP", reader)?;
147

            
148
14075
    let next_c = reader.peek();
149
14075
    match next_c {
150
        Some('/') => {
151
210
            let available_version = [
152
210
                ("/1.0", VersionValue::Version1),
153
210
                ("/1.1", VersionValue::Version11),
154
210
                ("/2", VersionValue::Version2),
155
210
                ("/3", VersionValue::Version3),
156
210
            ];
157
505
            for (s, value) in available_version.iter() {
158
505
                if try_literal(s, reader).is_ok() {
159
205
                    return Ok(Version {
160
205
                        value: *value,
161
205
                        source_info: SourceInfo::new(start.pos, reader.cursor().pos),
162
205
                    });
163
                }
164
            }
165
5
            Err(ParseError::new(start.pos, false, ParseErrorKind::Version))
166
        }
167
13865
        Some(' ') | Some('\t') => Ok(Version {
168
13865
            value: VersionAny,
169
13865
            source_info: SourceInfo::new(start.pos, reader.cursor().pos),
170
13865
        }),
171
        _ => Err(ParseError::new(start.pos, false, ParseErrorKind::Version)),
172
    }
173
}
174

            
175
14070
fn status(reader: &mut Reader) -> ParseResult<Status> {
176
14070
    let start = reader.cursor();
177
14070
    let value = match try_literal("*", reader) {
178
200
        Ok(_) => StatusValue::Any,
179
        Err(_) => {
180
13870
            if reader.is_eof() {
181
                let kind = ParseErrorKind::Status;
182
                return Err(ParseError::new(start.pos, false, kind));
183
            }
184
58234
            let s = reader.read_while(|c| c.is_ascii_digit());
185
13870
            if s.is_empty() {
186
5
                let kind = ParseErrorKind::Status;
187
5
                return Err(ParseError::new(start.pos, false, kind));
188
            }
189
13865
            match s.to_string().parse() {
190
13865
                Ok(value) => StatusValue::Specific(value),
191
                Err(_) => {
192
                    let kind = ParseErrorKind::Status;
193
                    return Err(ParseError::new(start.pos, false, kind));
194
                }
195
            }
196
        }
197
    };
198
14065
    let end = reader.cursor();
199
14065
    Ok(Status {
200
14065
        value,
201
14065
        source_info: SourceInfo::new(start.pos, end.pos),
202
14065
    })
203
}
204

            
205
29510
fn body(reader: &mut Reader) -> ParseResult<Body> {
206
    //  let start = reader.state.clone();
207
29510
    let line_terminators = optional_line_terminators(reader)?;
208
29510
    let space0 = zero_or_more_spaces(reader)?;
209
29510
    let value = bytes(reader)?;
210
3975
    let line_terminator0 = line_terminator(reader)?;
211
3975
    Ok(Body {
212
3975
        line_terminators,
213
3975
        space0,
214
3975
        value,
215
3975
        line_terminator0,
216
3975
    })
217
}
218

            
219
#[cfg(test)]
220
mod tests {
221
    use super::*;
222
    use crate::ast::{
223
        Bytes, Comment, JsonListElement, JsonValue, LineTerminator, MultilineString,
224
        MultilineStringKind, Template, TemplateElement, Whitespace,
225
    };
226
    use crate::reader::{CharPos, Pos};
227
    use crate::types::ToSource;
228

            
229
    #[test]
230
    fn test_hurl_file() {
231
        let mut reader = Reader::new("GET http://google.fr");
232
        let hurl_file = hurl_file(&mut reader).unwrap();
233
        assert_eq!(hurl_file.entries.len(), 1);
234
    }
235

            
236
    #[test]
237
    fn test_entry() {
238
        let mut reader = Reader::new("GET http://google.fr");
239
        let e = entry(&mut reader).unwrap();
240
        assert_eq!(e.request.method, Method::new("GET"));
241
        assert_eq!(reader.cursor().index, CharPos(20));
242
    }
243

            
244
    #[test]
245
    fn test_several_entry() {
246
        let mut reader = Reader::new("GET http://google.fr\nGET http://google.fr");
247

            
248
        let e = entry(&mut reader).unwrap();
249
        assert_eq!(e.request.method, Method::new("GET"));
250
        assert_eq!(reader.cursor().index, CharPos(21));
251
        assert_eq!(reader.cursor().pos.line, 2);
252

            
253
        let e = entry(&mut reader).unwrap();
254
        assert_eq!(e.request.method, Method::new("GET"));
255
        assert_eq!(reader.cursor().index, CharPos(41));
256
        assert_eq!(reader.cursor().pos.line, 2);
257

            
258
        let mut reader =
259
            Reader::new("GET http://google.fr # comment1\nGET http://google.fr # comment2");
260

            
261
        let e = entry(&mut reader).unwrap();
262
        assert_eq!(e.request.method, Method::new("GET"));
263
        assert_eq!(reader.cursor().index, CharPos(32));
264
        assert_eq!(reader.cursor().pos.line, 2);
265

            
266
        let e = entry(&mut reader).unwrap();
267
        assert_eq!(e.request.method, Method::new("GET"));
268
        assert_eq!(reader.cursor().index, CharPos(63));
269
        assert_eq!(reader.cursor().pos.line, 2);
270
    }
271

            
272
    #[test]
273
    fn test_entry_with_response() {
274
        let mut reader = Reader::new("GET http://google.fr\nHTTP/1.1 200");
275
        let e = entry(&mut reader).unwrap();
276
        assert_eq!(e.request.method, Method::new("GET"));
277
        assert_eq!(e.response.unwrap().status.value, StatusValue::Specific(200));
278
    }
279

            
280
    #[test]
281
    fn test_request() {
282
        let mut reader = Reader::new("GET http://google.fr");
283
        let default_request = Request {
284
            line_terminators: vec![],
285
            space0: Whitespace {
286
                value: String::new(),
287
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
288
            },
289
            method: Method::new("GET"),
290
            space1: Whitespace {
291
                value: " ".to_string(),
292
                source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 5)),
293
            },
294
            url: Template::new(
295
                None,
296
                vec![TemplateElement::String {
297
                    value: "http://google.fr".to_string(),
298
                    source: "http://google.fr".to_source(),
299
                }],
300
                SourceInfo::new(Pos::new(1, 5), Pos::new(1, 21)),
301
            ),
302
            line_terminator0: LineTerminator {
303
                space0: Whitespace {
304
                    value: String::new(),
305
                    source_info: SourceInfo::new(Pos::new(1, 21), Pos::new(1, 21)),
306
                },
307
                comment: None,
308
                newline: Whitespace {
309
                    value: String::new(),
310
                    source_info: SourceInfo::new(Pos::new(1, 21), Pos::new(1, 21)),
311
                },
312
            },
313
            headers: vec![],
314
            sections: vec![],
315
            body: None,
316
            source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 21)),
317
        };
318
        assert_eq!(request(&mut reader).unwrap(), default_request);
319
        assert_eq!(reader.cursor().index, CharPos(20));
320

            
321
        let mut reader = Reader::new("GET  http://google.fr # comment");
322
        let default_request = Request {
323
            line_terminators: vec![],
324
            space0: Whitespace {
325
                value: String::new(),
326
                source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 1)),
327
            },
328
            method: Method::new("GET"),
329
            space1: Whitespace {
330
                value: "  ".to_string(),
331
                source_info: SourceInfo::new(Pos::new(1, 4), Pos::new(1, 6)),
332
            },
333
            url: Template::new(
334
                None,
335
                vec![TemplateElement::String {
336
                    value: "http://google.fr".to_string(),
337
                    source: "http://google.fr".to_source(),
338
                }],
339
                SourceInfo::new(Pos::new(1, 6), Pos::new(1, 22)),
340
            ),
341
            line_terminator0: LineTerminator {
342
                space0: Whitespace {
343
                    value: " ".to_string(),
344
                    source_info: SourceInfo::new(Pos::new(1, 22), Pos::new(1, 23)),
345
                },
346
                comment: Some(Comment {
347
                    value: " comment".to_string(),
348
                    source_info: SourceInfo::new(Pos::new(1, 24), Pos::new(1, 32)),
349
                }),
350
                newline: Whitespace {
351
                    value: String::new(),
352
                    source_info: SourceInfo::new(Pos::new(1, 32), Pos::new(1, 32)),
353
                },
354
            },
355
            headers: vec![],
356
            sections: vec![],
357
            body: None,
358
            source_info: SourceInfo::new(Pos::new(1, 1), Pos::new(1, 32)),
359
        };
360
        assert_eq!(request(&mut reader).unwrap(), default_request);
361
        assert_eq!(reader.cursor().index, CharPos(31));
362

            
363
        let mut reader = Reader::new("GET http://google.fr\nGET http://google.fr");
364
        let r = request(&mut reader).unwrap();
365
        assert_eq!(r.method, Method::new("GET"));
366
        assert_eq!(reader.cursor().index, CharPos(21));
367
        let r = request(&mut reader).unwrap();
368
        assert_eq!(r.method, Method::new("GET"));
369
    }
370

            
371
    #[test]
372
    fn test_request_multilines() {
373
        // GET http://google.fr
374
        // ```
375
        // Hello World!
376
        // ```
377
        let mut reader = Reader::new("GET http://google.fr\n```\nHello World!\n```");
378
        let req = request(&mut reader).unwrap();
379
        assert_eq!(
380
            req.body.unwrap(),
381
            Body {
382
                line_terminators: vec![],
383
                space0: Whitespace {
384
                    value: String::new(),
385
                    source_info: SourceInfo::new(Pos::new(2, 1), Pos::new(2, 1)),
386
                },
387
                value: Bytes::MultilineString(MultilineString {
388
                    space: Whitespace {
389
                        value: String::new(),
390
                        source_info: SourceInfo::new(Pos::new(2, 4), Pos::new(2, 4)),
391
                    },
392
                    newline: Whitespace {
393
                        source_info: SourceInfo::new(Pos::new(2, 4), Pos::new(3, 1)),
394
                        value: "\n".to_string(),
395
                    },
396
                    kind: MultilineStringKind::Text(Template::new(
397
                        None,
398
                        vec![TemplateElement::String {
399
                            value: "Hello World!\n".to_string(),
400
                            source: "Hello World!\n".to_source(),
401
                        }],
402
                        SourceInfo::new(Pos::new(3, 1), Pos::new(4, 1)),
403
                    )),
404
                }),
405
                line_terminator0: LineTerminator {
406
                    space0: Whitespace {
407
                        value: String::new(),
408
                        source_info: SourceInfo::new(Pos::new(4, 4), Pos::new(4, 4)),
409
                    },
410
                    comment: None,
411
                    newline: Whitespace {
412
                        value: String::new(),
413
                        source_info: SourceInfo::new(Pos::new(4, 4), Pos::new(4, 4)),
414
                    },
415
                },
416
            }
417
        );
418
    }
419

            
420
    #[test]
421
    fn test_request_post_json() {
422
        let mut reader = Reader::new("POST http://localhost:8000/post-json-array\n[1,2,3]");
423
        let r = request(&mut reader).unwrap();
424
        assert_eq!(r.method, Method::new("POST"));
425
        assert_eq!(
426
            r.body.unwrap().value,
427
            Bytes::Json(JsonValue::List {
428
                space0: String::new(),
429
                elements: vec![
430
                    JsonListElement {
431
                        space0: String::new(),
432
                        value: JsonValue::Number("1".to_string()),
433
                        space1: String::new(),
434
                    },
435
                    JsonListElement {
436
                        space0: String::new(),
437
                        value: JsonValue::Number("2".to_string()),
438
                        space1: String::new(),
439
                    },
440
                    JsonListElement {
441
                        space0: String::new(),
442
                        value: JsonValue::Number("3".to_string()),
443
                        space1: String::new(),
444
                    },
445
                ],
446
            })
447
        );
448

            
449
        let mut reader = Reader::new("POST http://localhost:8000/post-json-string\n\"Hello\"");
450
        let r = request(&mut reader).unwrap();
451
        assert_eq!(r.method, Method::new("POST"));
452
        assert_eq!(
453
            r.body.unwrap().value,
454
            Bytes::Json(JsonValue::String(Template::new(
455
                Some('"'),
456
                vec![TemplateElement::String {
457
                    value: "Hello".to_string(),
458
                    source: "Hello".to_source(),
459
                }],
460
                SourceInfo::new(Pos::new(2, 2), Pos::new(2, 7)),
461
            )))
462
        );
463

            
464
        let mut reader = Reader::new("POST http://localhost:8000/post-json-number\n100");
465
        let r = request(&mut reader).unwrap();
466
        assert_eq!(r.method, Method::new("POST"));
467
        assert_eq!(
468
            r.body.unwrap().value,
469
            Bytes::Json(JsonValue::Number("100".to_string()))
470
        );
471
    }
472

            
473
    #[test]
474
    fn test_request_error() {
475
        let mut reader = Reader::new("xxx");
476
        let error = request(&mut reader).err().unwrap();
477
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
478
    }
479

            
480
    #[test]
481
    fn test_response() {
482
        let mut reader = Reader::new("HTTP/1.1 200");
483
        //println!("{:?}", response(&mut reader));
484
        let r = response(&mut reader).unwrap();
485

            
486
        assert_eq!(r.version.value, VersionValue::Version11);
487
        assert_eq!(r.status.value, StatusValue::Specific(200));
488
    }
489

            
490
    #[test]
491
    fn test_method() {
492
        let mut reader = Reader::new("xxx ");
493
        let error = method(&mut reader).err().unwrap();
494
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
495
        assert_eq!(reader.cursor().index, CharPos(3));
496

            
497
        let mut reader = Reader::new("");
498
        let error = method(&mut reader).err().unwrap();
499
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
500
        assert_eq!(reader.cursor().index, CharPos(0));
501

            
502
        let mut reader = Reader::new("GET ");
503
        assert_eq!(method(&mut reader).unwrap(), Method::new("GET"));
504
        assert_eq!(reader.cursor().index, CharPos(3));
505

            
506
        let mut reader = Reader::new("CUSTOM");
507
        assert_eq!(method(&mut reader).unwrap(), Method::new("CUSTOM"));
508
        assert_eq!(reader.cursor().index, CharPos(6));
509
    }
510

            
511
    #[test]
512
    fn test_version() {
513
        let mut reader = Reader::new("HTTP 200");
514
        assert_eq!(version(&mut reader).unwrap().value, VersionAny);
515
        assert_eq!(reader.cursor().index, CharPos(4));
516

            
517
        let mut reader = Reader::new("HTTP\t200");
518
        assert_eq!(version(&mut reader).unwrap().value, VersionAny);
519
        assert_eq!(reader.cursor().index, CharPos(4));
520

            
521
        let mut reader = Reader::new("HTTP/1.1 200");
522
        assert_eq!(version(&mut reader).unwrap().value, VersionValue::Version11);
523

            
524
        let mut reader = Reader::new("HTTP/1. 200");
525
        let error = version(&mut reader).err().unwrap();
526
        assert_eq!(error.pos, Pos { line: 1, column: 1 });
527
    }
528

            
529
    #[test]
530
    fn test_status() {
531
        let mut reader = Reader::new("*");
532
        let s = status(&mut reader).unwrap();
533
        assert_eq!(s.value, StatusValue::Any);
534

            
535
        let mut reader = Reader::new("200");
536
        let s = status(&mut reader).unwrap();
537
        assert_eq!(s.value, StatusValue::Specific(200));
538

            
539
        let mut reader = Reader::new("xxx");
540
        let result = status(&mut reader);
541
        assert!(result.is_err());
542
    }
543

            
544
    #[test]
545
    fn test_body_json() {
546
        let mut reader = Reader::new("[1,2,3] ");
547
        let b = body(&mut reader).unwrap();
548
        assert_eq!(b.line_terminators.len(), 0);
549
        assert_eq!(
550
            b.value,
551
            Bytes::Json(JsonValue::List {
552
                space0: String::new(),
553
                elements: vec![
554
                    JsonListElement {
555
                        space0: String::new(),
556
                        value: JsonValue::Number("1".to_string()),
557
                        space1: String::new(),
558
                    },
559
                    JsonListElement {
560
                        space0: String::new(),
561
                        value: JsonValue::Number("2".to_string()),
562
                        space1: String::new(),
563
                    },
564
                    JsonListElement {
565
                        space0: String::new(),
566
                        value: JsonValue::Number("3".to_string()),
567
                        space1: String::new(),
568
                    },
569
                ],
570
            })
571
        );
572
        assert_eq!(reader.cursor().index, CharPos(8));
573

            
574
        let mut reader = Reader::new("{}");
575
        let b = body(&mut reader).unwrap();
576
        assert_eq!(b.line_terminators.len(), 0);
577
        assert_eq!(
578
            b.value,
579
            Bytes::Json(JsonValue::Object {
580
                space0: String::new(),
581
                elements: vec![],
582
            })
583
        );
584
        assert_eq!(reader.cursor().index, CharPos(2));
585

            
586
        let mut reader = Reader::new("# comment\n {} # comment\nxxx");
587
        let b = body(&mut reader).unwrap();
588
        assert_eq!(b.line_terminators.len(), 1);
589
        assert_eq!(
590
            b.value,
591
            Bytes::Json(JsonValue::Object {
592
                space0: String::new(),
593
                elements: vec![],
594
            })
595
        );
596
        assert_eq!(reader.cursor().index, CharPos(24));
597

            
598
        let mut reader = Reader::new("{x");
599
        let error = body(&mut reader).err().unwrap();
600
        assert_eq!(error.pos, Pos { line: 1, column: 2 });
601
        assert!(!error.recoverable);
602
    }
603
}