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::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
4240
pub fn hurl_file(reader: &mut Reader) -> ParseResult<HurlFile> {
35
4240
    let entries = zero_or_more(entry, reader)?;
36
4000
    let line_terminators = optional_line_terminators(reader)?;
37
4000
    eof(reader)?;
38
4000
    Ok(HurlFile {
39
4000
        entries,
40
4000
        line_terminators,
41
4000
    })
42
}
43

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

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

            
66
15175
    check_duplicated_sections(&sections)?;
67

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

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

            
95
13765
    check_duplicated_sections(&sections)?;
96

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

            
111
28940
fn check_duplicated_sections(sections: &[Section]) -> Result<(), ParseError> {
112
28940
    let mut section_names = vec![];
113
40795
    for section in sections {
114
11865
        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
11855
        } else {
121
11855
            section_names.push(section.identifier());
122
        }
123
    }
124
28930
    Ok(())
125
}
126

            
127
16290
fn method(reader: &mut Reader) -> ParseResult<Method> {
128
16290
    if reader.is_eof() {
129
955
        let kind = ParseErrorKind::Method {
130
955
            name: "<EOF>".to_string(),
131
955
        };
132
955
        return Err(ParseError::new(reader.cursor().pos, true, kind));
133
    }
134
15335
    let start = reader.cursor();
135
67352
    let name = reader.read_while(|c| c.is_ascii_alphabetic());
136
15335
    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
15315
        Ok(Method::new(&name))
141
    }
142
}
143

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

            
148
13835
    let next_c = reader.peek();
149
13835
    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
520
            for (s, value) in available_version.iter() {
158
520
                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
13625
        Some(' ') | Some('\t') => Ok(Version {
168
13625
            value: VersionAny,
169
13625
            source_info: SourceInfo::new(start.pos, reader.cursor().pos),
170
13625
        }),
171
        _ => Err(ParseError::new(start.pos, false, ParseErrorKind::Version)),
172
    }
173
}
174

            
175
13830
fn status(reader: &mut Reader) -> ParseResult<Status> {
176
13830
    let start = reader.cursor();
177
13830
    let value = match try_literal("*", reader) {
178
200
        Ok(_) => StatusValue::Any,
179
        Err(_) => {
180
13630
            if reader.is_eof() {
181
                let kind = ParseErrorKind::Status;
182
                return Err(ParseError::new(start.pos, false, kind));
183
            }
184
57226
            let s = reader.read_while(|c| c.is_ascii_digit());
185
13630
            if s.is_empty() {
186
5
                let kind = ParseErrorKind::Status;
187
5
                return Err(ParseError::new(start.pos, false, kind));
188
            }
189
13625
            match s.to_string().parse() {
190
13625
                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
13825
    let end = reader.cursor();
199
13825
    Ok(Status {
200
13825
        value,
201
13825
        source_info: SourceInfo::new(start.pos, end.pos),
202
13825
    })
203
}
204

            
205
29005
fn body(reader: &mut Reader) -> ParseResult<Body> {
206
    //  let start = reader.state.clone();
207
29005
    let line_terminators = optional_line_terminators(reader)?;
208
29005
    let space0 = zero_or_more_spaces(reader)?;
209
29005
    let value = bytes(reader)?;
210
3815
    let line_terminator0 = line_terminator(reader)?;
211
3815
    Ok(Body {
212
3815
        line_terminators,
213
3815
        space0,
214
3815
        value,
215
3815
        line_terminator0,
216
3815
    })
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::typing::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
                    attributes: vec![],
389
                    space: Whitespace {
390
                        value: String::new(),
391
                        source_info: SourceInfo::new(Pos::new(2, 4), Pos::new(2, 4)),
392
                    },
393
                    newline: Whitespace {
394
                        source_info: SourceInfo::new(Pos::new(2, 4), Pos::new(3, 1)),
395
                        value: "\n".to_string(),
396
                    },
397
                    kind: MultilineStringKind::Text(Template::new(
398
                        None,
399
                        vec![TemplateElement::String {
400
                            value: "Hello World!\n".to_string(),
401
                            source: "Hello World!\n".to_source(),
402
                        }],
403
                        SourceInfo::new(Pos::new(3, 1), Pos::new(4, 1)),
404
                    )),
405
                }),
406
                line_terminator0: LineTerminator {
407
                    space0: Whitespace {
408
                        value: String::new(),
409
                        source_info: SourceInfo::new(Pos::new(4, 4), Pos::new(4, 4)),
410
                    },
411
                    comment: None,
412
                    newline: Whitespace {
413
                        value: String::new(),
414
                        source_info: SourceInfo::new(Pos::new(4, 4), Pos::new(4, 4)),
415
                    },
416
                },
417
            }
418
        );
419
    }
420

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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