http_rest_file/
parser.rs

1use self::model::{Multipart, RequestTarget, WithDefault};
2pub use crate::scanner::Scanner;
3use crate::{
4    error::{ErrorWithPartial, ParseError, ParseErrorDetails},
5    model,
6    model::{
7        CommentKind, DataSource, DispositionField, FileParseResult, Header, HttpRestFile,
8        HttpRestFileExtension, PartialRequest, RequestBody, RequestLine, RequestSettings,
9        ResponseHandler, SaveResponse, SettingsEntry, UrlEncodedParam,
10    },
11    scanner::{LineIterator, WS_CHARS},
12};
13pub use http::Uri;
14use std::{fs, str::FromStr};
15
16pub const REQUEST_SEPARATOR: &str = "###";
17pub const META_COMMENT_SLASH: &str = "//";
18pub const META_COMMENT_TAG: &str = "#";
19pub const DEFAULT_MULTIPART_BOUNDARY: &str = "--boundary--";
20
21pub struct Parser {}
22
23type ParseResult<T> = Result<(T, Vec<ParseErrorDetails>), ParseErrorDetails>;
24
25impl Parser {
26    pub const REST_FILE_EXTENSIONS: [&str; 2] = ["http", "rest"];
27
28    #[allow(dead_code)]
29    pub fn has_valid_extension<T: AsRef<std::path::Path>>(path: &T) -> bool {
30        match path.as_ref().extension() {
31            Some(extension) => Parser::REST_FILE_EXTENSIONS.contains(&extension.to_str().unwrap()),
32            _ => false,
33        }
34    }
35
36    /// Parse the contents of a file into a `model::HttpRestFile`
37    /// # Arguments
38    /// * `path` - path to a .http or .rest file
39    pub fn parse_file(path: &std::path::Path) -> Result<model::HttpRestFile, ParseError> {
40        if let Ok(content) = fs::read_to_string(path) {
41            let result = Parser::parse(&content, true);
42            Ok(HttpRestFile {
43                requests: result.requests,
44                errs: result.errs,
45                path: Box::new(path.to_owned()),
46                extension: HttpRestFileExtension::from_path(path),
47            })
48        } else {
49            Err(ParseError::CouldNotReadRequestFile(path.to_owned()))
50        }
51    }
52
53    /// Parse the contents of a request file as string into multiple requests within a
54    /// `model::FileParseResult`. This model contains all parsed requests as well as errors
55    /// encountered during parsing.
56    /// # Arguments
57    /// * `string` - string to parse
58    /// * `print_errors` - if set to true prints errors to the console
59    pub fn parse(string: &str, print_errors: bool) -> model::FileParseResult {
60        let mut scanner = Scanner::new(string);
61
62        let mut requests: Vec<model::Request> = Vec::new();
63        let mut errs: Vec<ErrorWithPartial> = Vec::new();
64
65        loop {
66            scanner.skip_empty_lines_and_ws();
67
68            if scanner.is_done() {
69                break;
70            }
71            match Parser::parse_request(&mut scanner) {
72                Ok(request) => {
73                    requests.push(request);
74                }
75                Err(err_with_partial) => {
76                    errs.push(err_with_partial);
77                }
78            }
79            scanner.skip_empty_lines();
80            scanner.skip_ws();
81
82            if scanner.is_done() {
83                break;
84            }
85
86            // go to next ### that should start a request
87            while let Some(line) = scanner.peek_line() {
88                if line.trim_start().starts_with(REQUEST_SEPARATOR) {
89                    break;
90                } else {
91                    scanner.skip_to_next_line();
92                }
93            }
94
95            scanner.skip_empty_lines();
96            scanner.skip_ws();
97
98            if scanner.is_done() {
99                break;
100            }
101        }
102
103        if !errs.is_empty() && print_errors {
104            eprintln!("{}", Parser::get_pretty_print_errs(&scanner, errs.iter()));
105        }
106        FileParseResult { requests, errs }
107    }
108
109    /// Parse a single request either until no further lines are present or a `REQUEST_SEPARATOR`
110    /// is encountered
111    pub fn parse_request(scanner: &mut Scanner) -> Result<model::Request, ErrorWithPartial> {
112        let mut comments = Vec::new();
113        let mut name: Option<String> = None;
114        let mut parse_errs: Vec<ParseErrorDetails> = Vec::new();
115        let mut settings = RequestSettings::default();
116        let mut pre_request_script: Option<model::PreRequestScript> = None;
117
118        scanner.skip_empty_lines();
119
120        loop {
121            // preq-request-scrip
122            if scanner.peek().map_or(false, |c| c == &'<') {
123                if let Ok(result) = Parser::parse_pre_request_script(scanner) {
124                    pre_request_script = result;
125                };
126                continue;
127            }
128            match Parser::parse_meta_comment_line(scanner) {
129                Some(Ok(SettingsEntry::NameEntry(entry_name))) => {
130                    if !entry_name.is_empty() {
131                        name = Some(entry_name);
132                    }
133                    continue;
134                }
135                Some(Ok(entry)) => {
136                    settings.set_entry(&entry);
137                    continue;
138                }
139                Some(Err(parse_error)) => {
140                    parse_errs.push(parse_error);
141                }
142                None => (), // ignore
143            }
144
145            match Parser::parse_comment(scanner) {
146                Ok(Some(comment_node)) => {
147                    comments.push(comment_node);
148                }
149                Ok(None) => {
150                    break;
151                }
152                Err(parse_error) => {
153                    parse_errs.push(parse_error);
154                    break;
155                }
156            }
157        }
158
159        // we only found comments and no request, in this case no request is present
160        if scanner.is_done() {
161            parse_errs.push(ParseErrorDetails {
162                error: ParseError::MissingRequestTargetLine,
163                details: None,
164                start_pos: Some(scanner.get_pos().cursor),
165                end_pos: None,
166            });
167            return Err(ErrorWithPartial {
168                partial_request: PartialRequest {
169                    name,
170                    comments,
171                    settings,
172                    request_line: None,
173                    body: None,
174                    pre_request_script,
175                    save_response: None,
176                    headers: None,
177                    response_handler: None,
178                },
179                details: parse_errs,
180            });
181        }
182
183        // if no name has been found with meta tag @name=, set name from a comment starting with
184        // '###' if there is any
185        if name.is_none() {
186            if let Some(position) = comments
187                .iter()
188                .position(|c| c.kind == CommentKind::RequestSeparator)
189            {
190                let comment = comments.remove(position).value.trim().to_string();
191                if !comment.is_empty() {
192                    name = Some(comment);
193                };
194            }
195        }
196
197        let request_line: Option<RequestLine> = match Parser::parse_request_line(scanner) {
198            Ok((request_line, errs)) => {
199                parse_errs.extend(errs);
200                Some(request_line)
201            }
202            Err(parse_error) => {
203                parse_errs.push(parse_error);
204                None
205            }
206        };
207
208        // end of request reached?
209        {
210            let peek_line = scanner.peek_line();
211            if peek_line.is_some() && peek_line.unwrap().trim().starts_with(REQUEST_SEPARATOR) {
212                if let Some(request_line) = request_line {
213                    let request_node = model::Request {
214                        name,
215                        comments,
216                        settings,
217                        pre_request_script,
218                        request_line,
219                        // no headers nor body parsed
220                        headers: vec![],
221                        body: RequestBody::None,
222                        response_handler: None,
223                        save_response: None,
224                    };
225                    return Ok(request_node);
226                } else {
227                    return Err(ErrorWithPartial {
228                        partial_request: PartialRequest {
229                            name,
230                            comments,
231                            settings,
232                            response_handler: None,
233                            pre_request_script: None,
234                            request_line: None,
235                            headers: None,
236                            save_response: None,
237                            body: None,
238                        },
239                        details: parse_errs,
240                    });
241                }
242            }
243        }
244
245        let headers = match Parser::parse_headers(scanner) {
246            Ok(headers) => headers,
247            Err(parse_err) => {
248                parse_errs.push(parse_err);
249                return Err(ErrorWithPartial {
250                    partial_request: PartialRequest {
251                        name,
252                        comments,
253                        settings,
254                        pre_request_script,
255                        request_line,
256                        headers: None,
257                        body: None,
258                        response_handler: None,
259                        save_response: None,
260                    },
261                    details: parse_errs,
262                });
263            }
264        };
265
266        scanner.skip_empty_lines();
267
268        let (body, body_errs) = match Parser::parse_body(scanner, &headers) {
269            Ok(body) => (body, Vec::<ParseErrorDetails>::new()),
270            Err((body, errs)) => (body, errs),
271        };
272
273        if !body_errs.is_empty() {
274            parse_errs.extend(body_errs.clone());
275        }
276
277        let response_handler = match Parser::parse_response_handler(scanner) {
278            Ok(result) => result,
279            Err(err) => {
280                parse_errs.push(err);
281                return Err(ErrorWithPartial {
282                    partial_request: PartialRequest {
283                        name,
284                        comments,
285                        settings,
286                        pre_request_script,
287                        request_line,
288                        headers: Some(headers),
289                        body: Some(body),
290                        response_handler: None,
291                        save_response: None,
292                    },
293                    details: parse_errs,
294                });
295            }
296        };
297
298        scanner.skip_empty_lines();
299
300        let save_response = match Parser::parse_redirect(scanner) {
301            Ok(result) => result,
302            Err(err) => {
303                parse_errs.push(err);
304                return Err(ErrorWithPartial {
305                    partial_request: PartialRequest {
306                        name,
307                        comments,
308                        settings,
309                        pre_request_script,
310                        request_line,
311                        headers: Some(headers),
312                        body: Some(body),
313                        response_handler,
314                        save_response: None,
315                    },
316                    details: parse_errs,
317                });
318            }
319        };
320        scanner.skip_empty_lines();
321
322        if !parse_errs.is_empty() {
323            return Err(ErrorWithPartial {
324                partial_request: PartialRequest {
325                    name,
326                    comments,
327                    settings,
328                    pre_request_script,
329                    request_line,
330                    headers: Some(headers),
331                    body: Some(body),
332                    response_handler,
333                    save_response,
334                },
335                details: parse_errs,
336            });
337        }
338
339        let mut request_node = model::Request {
340            name,
341            comments,
342            // we can unwrap as there were errors and we would have returned above
343            request_line: request_line.unwrap(),
344            headers,
345            body,
346            settings,
347            pre_request_script,
348            response_handler,
349            save_response,
350        };
351
352        // if no name set we use the first comment as name
353        // Only do this for comments not containing meta sign @ as these specify the request
354        // settings
355        if request_node.name.is_none() && !request_node.comments.is_empty() {
356            let name_pos = request_node
357                .comments
358                .iter()
359                .position(|com| !com.value.contains('@'));
360            if let Some(name_pos) = name_pos {
361                let name_comment = request_node.comments.remove(name_pos);
362                request_node.name = Some(name_comment.value);
363            }
364        }
365        Ok(request_node)
366    }
367
368    /// Get string for printing errors to the console
369    fn get_pretty_print_errs<'a, T>(scanner: &Scanner, errs: T) -> String
370    where
371        T: Iterator<Item = &'a ErrorWithPartial>,
372    {
373        errs.map(|err| &err.details)
374            .flatten()
375            .map(|err| Parser::pretty_err_string(scanner, err))
376            .collect::<Vec<String>>()
377            .join(&format!("\n{}\n", "-".repeat(50)))
378    }
379
380    fn pretty_err_string(scanner: &Scanner, err_details: &ParseErrorDetails) -> String {
381        let mut result = String::new();
382        result.push_str(&format!("Error: {}\n", err_details.error));
383        if err_details.start_pos.is_some() {
384            let error_context =
385                scanner.get_error_context(err_details.start_pos.unwrap(), err_details.end_pos);
386            result.push_str(&format!(
387                "Position: {}:{}\n",
388                error_context.line, error_context.column
389            ));
390            result.push_str(&error_context.context);
391        }
392        result
393    }
394
395    /// Parses the meta comment line that contains a name.
396    /// Assumes the comment characters ('//' or '#') for a comment have been stripped away
397    fn parse_meta_name(scanner: &mut Scanner) -> Result<Option<String>, ParseErrorDetails> {
398        scanner.skip_ws();
399
400        let name_regex = "\\s*@name\\s*=\\s*(.*)";
401        if let Ok(Some(captures)) = scanner.match_regex_forward(name_regex) {
402            let name = captures.first().unwrap().trim().to_string();
403            Ok(Some(name))
404        } else {
405            Ok(None)
406        }
407    }
408
409    /// Match a comment line after '###', '//' or '##' has been stripped from it
410    fn parse_comment_line(
411        scanner: &mut Scanner,
412        kind: CommentKind,
413    ) -> Result<Option<model::Comment>, ParseErrorDetails> {
414        scanner.skip_ws();
415        match scanner.seek_return(&'\n') {
416            Ok(value) => Ok(Some(model::Comment { value, kind })),
417            Err(_) => {
418                let position = scanner.get_pos().cursor;
419                let err_details = ParseErrorDetails::new_with_position(
420                    ParseError::MissingRequestTargetLine,
421                    (position, None),
422                );
423                Err(err_details)
424            }
425        }
426    }
427    /// match a comment line after '###', '//' or '##' has been stripped from it
428    fn parse_meta_comment_line(
429        scanner: &mut Scanner,
430    ) -> Option<Result<SettingsEntry, ParseErrorDetails>> {
431        scanner.skip_ws();
432
433        let peek_line = scanner.peek_line();
434
435        #[allow(clippy::question_mark)]
436        if peek_line.is_none() {
437            return None;
438        }
439
440        let mut line_scanner = Scanner::new(&peek_line.unwrap());
441        line_scanner.skip_ws();
442
443        if line_scanner.match_str_forward(META_COMMENT_SLASH)
444            || line_scanner.match_str_forward(META_COMMENT_TAG)
445        {
446            if let Ok(Some(name)) = Parser::parse_meta_name(&mut line_scanner) {
447                scanner.skip_to_next_line();
448                if !name.is_empty() {
449                    return Some(Ok(SettingsEntry::NameEntry(name)));
450                } else {
451                    return None;
452                }
453            }
454            let line = line_scanner.peek_line();
455            #[allow(clippy::question_mark)]
456            if line.is_none() {
457                return None;
458            }
459
460            let result: Option<Result<SettingsEntry, ParseErrorDetails>> =
461                match line.unwrap().trim() {
462                    "@no-cookie-jar" => Some(Ok(SettingsEntry::NoCookieJar)),
463                    "@no-redirect" => Some(Ok(SettingsEntry::NoRedirect)),
464                    "@no-log" => Some(Ok(SettingsEntry::NoLog)),
465                    // Non matching meta comment lines are taken as regular comments
466                    _ => None,
467                };
468
469            if result.is_some() {
470                scanner.skip_to_next_line();
471            }
472
473            return result;
474        }
475
476        None
477    }
478
479    /// Parse pre request scripts, which are either a path to a javascript file or blocks of text containing javascript code within '{% %}' blocks
480    /// The full script is parsed as a single string if '{% %}' blocks are present otherwise a path is parsed.
481    /// See also the `parse_response_handler` which parses similarly code that handles a response.
482    fn parse_pre_request_script(
483        scanner: &mut Scanner,
484    ) -> Result<Option<model::PreRequestScript>, ParseErrorDetails> {
485        if !scanner.take(&'<') {
486            return Ok(None);
487        };
488        let start_pos = scanner.get_pos();
489        scanner.skip_ws();
490        if !scanner.match_str_forward("{%") {
491            // if no starting script is found then a handler script should be presnet
492            let line = scanner.get_line_and_advance();
493            if line.is_none() {
494                let details = ParseErrorDetails {
495                    error: ParseError::MissingPreRequestScript,
496                    details: Some("When a '<' character is encountered before the request target line you can either specify a path to a file whose content will be inserted".to_string()),
497                    start_pos: Some(start_pos.cursor),
498                    end_pos: Some(scanner.get_cursor()),
499                };
500
501                return Err(details);
502            }
503            return Ok(Some(model::PreRequestScript::FromFilepath(
504                line.unwrap().trim().to_string(),
505            )));
506        }
507
508        let mut found: bool = false;
509        let mut lines: Vec<String> = Vec::new();
510        loop {
511            if let Ok(Some(result)) = scanner.match_regex_forward("(.*)%}") {
512                if result.len() == 1 {
513                    lines.push(result[0].to_string());
514                    found = true;
515                    break;
516                } else {
517                    let details = ParseErrorDetails::new_with_position(
518                        ParseError::MissingPreRequestScriptClose,
519                        (start_pos.cursor, Some(scanner.get_cursor())),
520                    );
521                    return Err(details);
522                }
523            } else {
524                let line = scanner.get_line_and_advance();
525                if line.is_none() {
526                    break;
527                }
528
529                lines.push(line.unwrap());
530            }
531        }
532
533        if !found {
534            let details = ParseErrorDetails::new_with_position(
535                ParseError::MissingPreRequestScriptClose,
536                (start_pos.cursor, Some(scanner.get_cursor())),
537            );
538            return Err(details);
539        }
540        scanner.skip_to_next_line();
541        Ok(Some(model::PreRequestScript::Script(lines.join("\n"))))
542    }
543    // @TODO: create a macro that generates a match statement for each enum variant
544    fn match_request_method(str: &str) -> model::HttpMethod {
545        // if not one of the well known methods then it is a custom method
546        model::HttpMethod::new(str)
547    }
548
549    /// Parse a request line of the form '[method required-whitespace] request-target [required-whitespace http-version]'
550    fn parse_request_line(scanner: &mut Scanner) -> ParseResult<model::RequestLine> {
551        let mut line = match scanner.get_line_and_advance() {
552            Some(line) => line,
553            _ => String::new(),
554        };
555
556        let line_start = scanner.get_pos();
557        // request line can be split over multiple lines but all lines following need to be
558        // indented
559        let line_iterator: LineIterator = scanner.iter_at_pos();
560
561        let (indented_lines, line_end): (Vec<String>, usize) =
562            line_iterator.take_while_peek(|line| {
563                !line.is_empty() && WS_CHARS.contains(&line.chars().next().unwrap())
564            });
565
566        scanner.set_pos(line_end);
567
568        if !indented_lines.is_empty() {
569            line.push_str(
570                &indented_lines
571                    .iter()
572                    .map(|l| l.trim().to_owned())
573                    .collect::<Vec<String>>()
574                    .join(""),
575            );
576        }
577
578        let line_scanner = Scanner::new(&line);
579        let tokens: Vec<String> = line_scanner.get_tokens();
580
581        // It can be that the request line is missing but there are still headers
582        if tokens.len() >= 2 && tokens[0].contains(':') {
583            return Err(ParseErrorDetails {
584                error: ParseError::MissingRequestTargetLine,
585                details: None,
586                start_pos: Some(line_start.cursor),
587                end_pos: None,
588            });
589        }
590
591        let (request_line, err): (model::RequestLine, Option<ParseErrorDetails>) = match &tokens[..]
592        {
593            [target_str] => (
594                model::RequestLine {
595                    target: RequestTarget::from(&target_str[..]),
596                    method: model::WithDefault::default(),
597                    http_version: model::WithDefault::default(),
598                },
599                None,
600            ),
601            [method, target_str] => (
602                model::RequestLine {
603                    target: RequestTarget::from(&target_str[..]),
604                    method: WithDefault::Some(Parser::match_request_method(method)),
605                    http_version: WithDefault::default(),
606                },
607                None,
608            ),
609
610            [method, target_str, http_version_str] => {
611                let result = model::HttpVersion::from_str(http_version_str);
612                let (http_version, http_version_err) = match result {
613                    Ok(version) => (WithDefault::Some(version), None),
614                    Err(err) => (WithDefault::default(), Some(err)),
615                };
616
617                let line_end = line_start.cursor + tokens.len();
618                (
619                    model::RequestLine {
620                        target: RequestTarget::from(&target_str[..]),
621                        method: WithDefault::Some(Parser::match_request_method(method)),
622                        http_version,
623                    },
624                    http_version_err.map(|err| {
625                        ParseErrorDetails::new_with_position(
626                            err,
627                            (line_start.cursor, Some(line_end)),
628                        )
629                    }),
630                )
631            }
632            //
633            [] => {
634                return Err(ParseErrorDetails {
635                    error: ParseError::MissingRequestTargetLine,
636                    details: None,
637                    start_pos: Some(line_start.cursor),
638                    end_pos: None,
639                });
640            } // on a request line only method, target and http_version should be present
641            [method, target_str, http_version_str, ..] => {
642                let result = model::HttpVersion::from_str(http_version_str);
643                let http_version = match result {
644                    Ok(version) => Some(version),
645                    Err(_) => None,
646                };
647
648                let error_details = ParseErrorDetails::new_with_position(
649                    ParseError::TooManyElementsOnRequestLine(tokens[3..].join(",")),
650                    (line_start.cursor, Some(line_end)),
651                );
652
653                (
654                    model::RequestLine {
655                        target: RequestTarget::from(&target_str[..]),
656                        method: WithDefault::Some(Parser::match_request_method(method)),
657                        http_version: WithDefault::from(http_version),
658                    },
659                    Some(error_details),
660                )
661            }
662        };
663
664        let mut errs: Vec<ParseErrorDetails> = Vec::new();
665        if let Some(err) = err {
666            errs.push(err);
667        }
668        Ok((request_line, errs))
669    }
670
671    /// Parse a regular comment either starts with '###' or with '//' or '#'
672    /// Both '//' and '#' comments may contain meta information, in this case they are not parsed
673    /// as regular comments. If a '###' comment occurs alone without any other comments, then it
674    /// signifies the name of a request and will be transformed afterwards and not taken as regular
675    /// comment.
676    /// Note that '###' can also be a request separator
677    fn parse_comment(scanner: &mut Scanner) -> Result<Option<model::Comment>, ParseErrorDetails> {
678        scanner.skip_empty_lines();
679        // comments can be indented
680        scanner.skip_ws();
681
682        if scanner.match_str_forward(CommentKind::RequestSeparator.string_repr()) {
683            return Parser::parse_comment_line(scanner, CommentKind::RequestSeparator);
684        }
685
686        if scanner.match_str_forward(CommentKind::DoubleSlash.string_repr()) {
687            return Parser::parse_comment_line(scanner, CommentKind::DoubleSlash);
688        }
689
690        // @TODO: is single comment allowed if not a name comment line?
691        if scanner.match_str_forward(CommentKind::SingleTag.string_repr()) {
692            return Parser::parse_comment_line(scanner, CommentKind::SingleTag);
693        }
694
695        Ok(None)
696    }
697
698    /// Parse http headers, they can either belong to a request or each multipart part can also
699    /// contain headers. This function is used to parse both cases.
700    fn parse_headers(scanner: &mut Scanner) -> Result<Vec<model::Header>, ParseErrorDetails> {
701        let mut headers: Vec<model::Header> = Vec::new();
702
703        let header_regex = regex::Regex::from_str("^([^:]+):\\s*(.+)\\s*").unwrap();
704
705        loop {
706            if scanner.is_done() {
707                return Ok(headers);
708            }
709
710            // newline after requestline and headers ends header section
711            if let Some(&'\n') = scanner.peek() {
712                return Ok(headers);
713            }
714
715            let line = scanner.get_line_and_advance().unwrap();
716            let captures = header_regex.captures(&line);
717
718            if captures.is_none() {
719                let err_details = ParseErrorDetails::new_with_position(
720                    ParseError::InvalidHeaderField(line),
721                    (scanner.get_cursor(), None),
722                );
723                return Err(err_details);
724            }
725            let captures = captures.unwrap();
726            match (captures.get(1), captures.get(2)) {
727                (Some(key_match), Some(value_match)) => {
728                    //@TODO: validate header fields
729                    headers.push(model::Header {
730                        key: key_match.as_str().to_string(),
731                        value: value_match.as_str().to_string(),
732                    })
733                }
734                _ => {
735                    let err_details = ParseErrorDetails::new_with_position(
736                        ParseError::InvalidHeaderField(line),
737                        (scanner.get_cursor(), None),
738                    );
739                    return Err(err_details);
740                }
741            }
742        }
743    }
744
745    /// Parse the body of an http request. Can either be multipart or contain some kind of data.
746    /// The Jetbrains client trims the data so trailing newlines or whitespace is also ignored when
747    /// parsing here
748    fn parse_body(
749        scanner: &mut Scanner,
750        headers: &[Header],
751    ) -> Result<RequestBody, (RequestBody, Vec<ParseErrorDetails>)> {
752        let mut parse_errs: Vec<ParseErrorDetails> = Vec::new();
753        let content_type = headers
754            .iter()
755            .find(|header| {
756                header.key == "Content-Type" //&& header.value.starts_with("multipart/form-data")
757            })
758            .map(|header| header.value.as_str());
759
760        let body = match content_type {
761            Some(content_type) if content_type.starts_with("multipart/form-data") => {
762                Parser::parse_content_type_multipart_form_data(
763                    scanner,
764                    content_type,
765                    &mut parse_errs,
766                )
767                .unwrap_or(RequestBody::None)
768            }
769            Some("application/x-www-form-urlencoded") => Parser::parse_body_urlencoded(scanner),
770            _ => {
771                let body = Parser::parse_raw_body(scanner);
772                // if we have a content-type then we just have an empty body instead of none
773                if content_type.is_some() && matches!(body, RequestBody::None) {
774                    RequestBody::Raw {
775                        data: DataSource::Raw(String::new()),
776                    }
777                } else {
778                    body
779                }
780            }
781        };
782
783        if parse_errs.is_empty() {
784            Ok(body)
785        } else {
786            Err((body, parse_errs))
787        }
788    }
789
790    fn parse_content_type_multipart_form_data(
791        scanner: &mut Scanner,
792        content_type: &str,
793        parse_errs: &mut Vec<ParseErrorDetails>,
794    ) -> Option<RequestBody> {
795        let boundary_regex =
796            regex::Regex::from_str("multipart/form-data\\s*(;\\s*boundary\\s*=\\s*(.+))?").unwrap();
797        let captures = boundary_regex.captures(content_type);
798
799        let mut boundary = DEFAULT_MULTIPART_BOUNDARY.to_string();
800
801        if let Some(captures) = captures {
802            let boundary_match = captures.get(2);
803
804            // either with or without quotes
805            if boundary_match.is_none() {
806                parse_errs.push(ParseErrorDetails::new_with_position(
807                    ParseError::MissingMultipartHeaderBoundaryDefinition(
808                        DEFAULT_MULTIPART_BOUNDARY.to_string(),
809                    ),
810                    (scanner.get_cursor(), None),
811                ));
812            }
813            boundary = boundary_match
814                .map(|o| o.as_str())
815                .unwrap_or(DEFAULT_MULTIPART_BOUNDARY)
816                .to_string();
817            if boundary.starts_with('"') && boundary.ends_with('"') {
818                boundary = boundary[1..(boundary.len() - 1)].to_string();
819            }
820        } else {
821            parse_errs.push(ParseErrorDetails::new_with_position(
822                ParseError::MissingMultipartHeaderBoundaryDefinition(
823                    DEFAULT_MULTIPART_BOUNDARY.to_string(),
824                ),
825                (scanner.get_cursor(), None),
826            ));
827        }
828        if let Err(boundary_err) = Parser::is_multipart_boundary_valid(&boundary) {
829            parse_errs.push(boundary_err);
830        }
831        match Parser::parse_multipart_body(scanner, &boundary, parse_errs) {
832            Ok(multipart_body) => Some(multipart_body),
833            Err(err) => {
834                parse_errs.push(err);
835                None
836            }
837        }
838    }
839
840    fn parse_body_urlencoded(scanner: &mut Scanner) -> RequestBody {
841        let mut url_encoded_params: Vec<UrlEncodedParam> = Vec::new();
842        if let Some(line) = scanner.peek_line() {
843            let line = line.trim();
844            if line.starts_with(REQUEST_SEPARATOR) {
845                return RequestBody::UrlEncoded { url_encoded_params };
846            }
847            scanner.skip_to_next_line();
848            url_encoded_params = line
849                .split('&')
850                .map(|key_val| {
851                    let mut split = key_val.split('=');
852                    let key = split.next();
853                    let value = split.next();
854                    UrlEncodedParam::new(key.unwrap_or_default(), value.unwrap_or_default())
855                })
856                .collect::<Vec<UrlEncodedParam>>();
857        }
858
859        RequestBody::UrlEncoded { url_encoded_params }
860    }
861
862    fn parse_raw_body(scanner: &mut Scanner) -> RequestBody {
863        if scanner.is_done() {
864            return RequestBody::None;
865        }
866
867        let start_pos = scanner.get_pos();
868        loop {
869            let peek_line = scanner.peek_line();
870            if peek_line.is_none() {
871                break;
872            }
873            let peek_line = peek_line.unwrap();
874            // new request starts
875            if peek_line.starts_with(REQUEST_SEPARATOR) {
876                break;
877            }
878
879            // response handler
880            if peek_line.starts_with('>') {
881                // if previous line is empty then do not parse it as body before response
882                // handler, when serializing we put an additional new line for clarity that
883                // should not be part of the body
884                if scanner
885                    .get_prev_line()
886                    .map_or(false, |l| l.trim().is_empty())
887                {
888                    scanner.step_to_previous_line_start();
889                }
890                break;
891            }
892
893            // output handler / redirect also ends body
894            if peek_line.starts_with(">>") {
895                // if previous line is empty then do not parse it as body before redirect
896                // when serializing we add an additional newline before the redirect for
897                // clarity which should not be part of the body
898                if scanner
899                    .get_prev_line()
900                    .map_or(false, |l| l.trim().is_empty())
901                {
902                    scanner.step_to_previous_line_start();
903                }
904                break;
905            }
906            scanner.skip_to_next_line();
907        }
908        let mut end_pos = scanner.get_pos();
909        if start_pos > end_pos {
910            end_pos = start_pos.clone();
911        }
912        let body_str = scanner.get_from_to(start_pos, end_pos);
913        if body_str.trim().starts_with('<') {
914            let path = body_str.split('<').nth(1).unwrap().trim();
915            RequestBody::Raw {
916                data: DataSource::FromFilepath(path.to_string()),
917            }
918        } else if !body_str.is_empty() {
919            // We trim trailing newlines, jetbrains client does the same
920            // However, this means a text body cannot contain trailing newlines @TODO
921            RequestBody::Raw {
922                data: DataSource::Raw(body_str.trim_end_matches('\n').to_string()),
923            }
924        } else {
925            RequestBody::None
926        }
927    }
928
929    /// Parse a multipart http body
930    fn parse_multipart_body(
931        scanner: &mut Scanner,
932        boundary: &str,
933        parse_errs: &mut Vec<ParseErrorDetails>,
934    ) -> Result<RequestBody, ParseErrorDetails> {
935        scanner.skip_empty_lines();
936
937        let mut parts: Vec<Multipart> = Vec::new();
938
939        let mut errors: Vec<ParseErrorDetails> = Vec::new();
940        loop {
941            let multipart = Parser::parse_multipart_part(scanner, boundary, parse_errs);
942            if let Err(err) = multipart {
943                errors.push(err);
944                break;
945            }
946            let multipart = multipart.unwrap();
947            parts.push(multipart);
948            if scanner.is_done() {
949                break;
950            }
951
952            let end_boundary = format!("--{}--", boundary);
953            // end of multipart
954            let end_boundary = regex::escape(&end_boundary);
955            if scanner.match_str_forward(&end_boundary) {
956                break;
957            }
958
959            let next_boundary = format!("--{}", boundary);
960            if !scanner.match_str_forward(&next_boundary) {
961                let err_details = ParseErrorDetails::new_with_position(
962                    ParseError::MissingMultipartBoundary {
963                        next_boundary,
964                        end_boundary,
965                    },
966                    (scanner.get_cursor(), None),
967                );
968                return Err(err_details);
969            }
970        }
971        Ok(RequestBody::Multipart {
972            boundary: boundary.to_string(),
973            parts,
974        })
975    }
976
977    /// Parse a single block of a multipart body
978    fn parse_multipart_part(
979        scanner: &mut Scanner,
980        boundary: &str,
981        parse_errs: &mut Vec<ParseErrorDetails>,
982    ) -> Result<model::Multipart, ParseErrorDetails> {
983        let boundary_line = format!("--{}", boundary);
984        let multipart_end_line = format!("--{}--", boundary);
985
986        let escaped_boundary = regex::escape(&boundary_line);
987        let first_boundary = scanner.match_regex_forward(&escaped_boundary);
988        if first_boundary.is_err() {
989            return Err(ParseErrorDetails::new_with_position(
990                ParseError::MissingMultipartStartingBoundary,
991                (scanner.get_cursor(), None),
992            ));
993        }
994
995        scanner.skip_to_next_line(); // @TODO: nothing else should be here
996
997        let start_pos = scanner.get_pos();
998
999        let part_headers = Parser::parse_headers(scanner).map_err(|err| {
1000            ParseErrorDetails::new_with_position(
1001                ParseError::InvalidSingleMultipartHeaders {
1002                    header_parse_err: Box::new(err.error.clone()),
1003                    error_msg: err.error.to_string(),
1004                },
1005                (scanner.get_cursor(), None),
1006            )
1007        })?;
1008        let end_pos = scanner.get_pos();
1009
1010        let (field, part_headers) = match &part_headers[..] {
1011            [] => {
1012                return Err(ParseErrorDetails::new_with_position(
1013                    ParseError::MissingSingleMultipartContentDispositionHeader,
1014                    (start_pos.cursor, Some(end_pos.cursor)),
1015                ));
1016            }
1017            [disposition_part, part_headers @ ..] => {
1018                if disposition_part.key != "Content-Disposition" {
1019                    return Err(ParseErrorDetails::new_with_position(
1020                        ParseError::WrongMultipartContentDispositionHeader(
1021                            disposition_part.key.clone(),
1022                        ),
1023                        (start_pos.cursor, Some(end_pos.cursor)),
1024                    ));
1025                }
1026                let parts: Vec<&str> = disposition_part.value.split(';').collect();
1027                let mut parts_iter = parts.iter();
1028                let disposition_type = parts_iter.next().unwrap().trim();
1029                if disposition_type != "form-data" {
1030                    // only form-data is valid in http context, other disposition types may exist
1031                    // for other applications (email mime types...)
1032                    return Err(ParseErrorDetails::new_with_position(
1033                        ParseError::InvalidMultipartContentDispositionFormData(
1034                            disposition_type.to_string(),
1035                        ),
1036                        (start_pos.cursor, Some(end_pos.cursor)),
1037                    ));
1038                }
1039                let mut disposition_field = DispositionField::new_with_filename("", None::<String>);
1040                for current in parts_iter {
1041                    match current.split('=').map(|p| p.trim()).collect::<Vec<&str>>()[..] {
1042                        [key, mut value] => {
1043                            if value.starts_with('"') && value.ends_with('"') {
1044                                value = &value[1..(value.len() - 1)];
1045                            }
1046                            if key == "filename" {
1047                                disposition_field.filename = Some(value.to_string());
1048                            } else if key == "filename*" {
1049                                disposition_field.filename_star = Some(value.to_string());
1050                            } else if key == "name" {
1051                                disposition_field.name = value.to_string();
1052                            }
1053                        }
1054                        _ => {
1055                            return Err(ParseErrorDetails::from(
1056                                ParseError::MalformedContentDispositionEntries(current.to_string()),
1057                            ))
1058                        }
1059                    }
1060                }
1061                (disposition_field, part_headers)
1062            }
1063        };
1064
1065        if field.name.is_empty() {
1066            let msg = format!(
1067                "[{}]",
1068                part_headers
1069                    .iter()
1070                    .map(|header| header.to_string())
1071                    .collect::<Vec<String>>()
1072                    .join(", ")
1073            );
1074            parse_errs.push(ParseErrorDetails::new_with_position(
1075                ParseError::SingleMultipartNameMissing(msg),
1076                (start_pos.cursor, Some(end_pos.cursor)),
1077            ));
1078        }
1079
1080        if !scanner.match_str_forward("\n") {
1081            return Err(ParseErrorDetails::new_with_position(
1082                ParseError::SingleMultipartMissingEmptyLine,
1083                (scanner.get_cursor(), None),
1084            ));
1085        }
1086
1087        let peek_line = scanner.peek_line();
1088
1089        if peek_line.is_none() {
1090            return Err(ParseErrorDetails {
1091                error: ParseError::MultipartShouldBeEndedWithBoundary(multipart_end_line),
1092                ..Default::default()
1093            });
1094        }
1095
1096        let peek_line = peek_line.unwrap();
1097
1098        // < means content of multipart is read from file
1099        // should only have one line to parse
1100        // @TODO only read in file depending on the content type -> how is this not ambigous?
1101        // @TODO can we have multiple files added here?
1102        if peek_line.starts_with('<') {
1103            let mut line = scanner.get_line_and_advance().unwrap();
1104            line = line.trim().to_string();
1105
1106            let file_path = &line[1..].trim();
1107            // @TODO is name expected?
1108            Ok(Multipart {
1109                disposition: field,
1110                headers: part_headers.to_vec(),
1111                data: DataSource::FromFilepath(file_path.to_string()), // @TODO: when to read in data from file?
1112            })
1113        } else {
1114            let mut text = String::new();
1115
1116            loop {
1117                let peek_line = scanner.peek_line();
1118                if peek_line.is_none() {
1119                    return Err(ParseErrorDetails {
1120                        error: ParseError::MultipartShouldBeEndedWithBoundary(multipart_end_line),
1121                        ..Default::default()
1122                    });
1123                };
1124                let peek_line = peek_line.unwrap();
1125                if peek_line == boundary_line || peek_line == multipart_end_line {
1126                    return Ok(Multipart {
1127                        disposition: field,
1128                        headers: part_headers.to_owned(),
1129                        data: DataSource::Raw(text),
1130                    });
1131                }
1132                let next = scanner.get_line_and_advance().unwrap();
1133                text += &next;
1134                // only add a new line if more text will appear
1135                if !scanner
1136                    .peek_line()
1137                    .map_or(false, |pl| pl.starts_with(&boundary_line))
1138                {
1139                    text += "\n";
1140                }
1141            }
1142        }
1143    }
1144
1145    /// Checks whether a multipart boundary is valid or not according to: https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1
1146    fn is_multipart_boundary_valid(boundary: &str) -> Result<(), ParseErrorDetails> {
1147        let boundary_len = boundary.len();
1148        if !(1..=70).contains(&boundary_len) {
1149            return Err(ParseErrorDetails {
1150                error: ParseError::InvalidMultipartBoundaryLength,
1151                ..Default::default()
1152            });
1153        }
1154
1155        let bytes = boundary.as_bytes();
1156        for byte in bytes {
1157            match byte {
1158                b'0'..=b'9'
1159                | b'a'..=b'z'
1160                | b'A'..=b'Z'
1161                | b'\''
1162                | b'('
1163                | b')'
1164                | b'.'
1165                | b','
1166                | b'-'
1167                | b'_'
1168                | b'+'
1169                | b'/'
1170                | b':'
1171                | b'?'
1172                | b'=' => continue,
1173                invalid_byte => {
1174                    return Err(ParseErrorDetails {
1175                        error: ParseError::InvalidMultipartBoundaryCharacter(
1176                            String::from_utf8(vec![invalid_byte.to_owned()]).unwrap(),
1177                        ),
1178                        ..Default::default()
1179                    });
1180                }
1181            }
1182        }
1183        Ok(())
1184    }
1185
1186    /// Parse a response handler. The http client can also pass the response data to a javascript block or to javascript code
1187    /// within a file if given as a path. This function parses either a path or the script as
1188    /// string similar to the `parse_pre_request_script` function.
1189    fn parse_response_handler(
1190        scanner: &mut Scanner,
1191    ) -> Result<Option<model::ResponseHandler>, ParseErrorDetails> {
1192        scanner.skip_empty_lines();
1193        scanner.skip_ws();
1194        let next_two = scanner.peek_n(2);
1195        if next_two.is_none() {
1196            return Ok(None);
1197        }
1198        let next_two = next_two.unwrap();
1199        if next_two[0] != '>' || next_two[1] == '>' {
1200            return Ok(None);
1201        }
1202
1203        if !scanner.take(&'>') {
1204            return Ok(None);
1205        }
1206        scanner.skip_ws();
1207        scanner.skip_empty_lines();
1208        let start_pos = scanner.get_pos();
1209        if scanner.match_str_forward("{%") {
1210            let mut lines: Vec<String> = Vec::new();
1211            let mut found = false;
1212            loop {
1213                if let Ok(Some(matches)) = scanner.match_regex_forward("(.*)%}") {
1214                    for m in matches {
1215                        found = true;
1216                        lines.push(m.to_string());
1217                    }
1218                    if found {
1219                        break;
1220                    }
1221                } else {
1222                    let line = scanner.get_line_and_advance();
1223                    if line.is_none() {
1224                        break;
1225                    }
1226                    lines.push(line.unwrap());
1227                }
1228            }
1229            if !found {
1230                return Err(ParseErrorDetails::new_with_position(
1231                    ParseError::MissingResponseHandlerClose,
1232                    (start_pos.cursor, Some(scanner.get_cursor())),
1233                ));
1234            }
1235
1236            scanner.skip_to_next_line();
1237
1238            Ok(Some(ResponseHandler::Script(lines.join("\n"))))
1239        } else {
1240            let path = scanner.get_line_and_advance();
1241            if path.is_none() || path.as_ref().unwrap().is_empty() {
1242                return Err(ParseErrorDetails::new_with_position(
1243                    ParseError::MissingResponseHandlerClose,
1244                    (scanner.get_cursor(), None::<usize>),
1245                ));
1246            }
1247
1248            return Ok(Some(ResponseHandler::FromFilepath(
1249                path.unwrap().trim().to_string(),
1250            )));
1251        }
1252    }
1253
1254    /// Parse a redirect line. A redirect can specify where the response of an http request should
1255    /// be saved. A redirect line either has the form `>> <some/path>` or `>>! <some/path>`
1256    fn parse_redirect(scanner: &mut Scanner) -> Result<Option<SaveResponse>, ParseErrorDetails> {
1257        scanner.skip_empty_lines();
1258        let start_pos = scanner.get_pos();
1259        if !scanner.match_str_forward(">>") {
1260            return Ok(None);
1261        }
1262
1263        let mut rewrite = false;
1264        if scanner.take(&'!') {
1265            rewrite = true;
1266        }
1267
1268        let path = scanner.get_line_and_advance();
1269
1270        if path.is_none() {
1271            return Err(ParseErrorDetails::new_with_position(
1272                ParseError::MissingResponseOutputPath,
1273                (start_pos.cursor, Some(scanner.get_cursor())),
1274            ));
1275        }
1276
1277        let path = path.unwrap().trim().to_string();
1278
1279        if rewrite {
1280            Ok(Some(SaveResponse::RewriteFile(std::path::PathBuf::from(
1281                path,
1282            ))))
1283        } else {
1284            Ok(Some(SaveResponse::NewFileIfExists(
1285                std::path::PathBuf::from(path),
1286            )))
1287        }
1288    }
1289}
1290
1291#[cfg(test)]
1292mod tests {
1293    use crate::{
1294        model::{Comment, DispositionField, HttpMethod, Request, RequestLine},
1295        parser::model::{Header, HttpVersion},
1296    };
1297
1298    use super::*;
1299
1300    #[test]
1301    pub fn name_triple_tag() {
1302        let str = "
1303### test name
1304
1305https://httpbin.org
1306";
1307        let parsed = Parser::parse(str, false);
1308
1309        let expected = vec![model::Request {
1310            name: Some(String::from("test name")),
1311            comments: Vec::new(),
1312            request_line: model::RequestLine {
1313                method: WithDefault::default(),
1314                target: RequestTarget::from("https://httpbin.org"),
1315                http_version: WithDefault::default(),
1316            },
1317            headers: Vec::new(),
1318            body: model::RequestBody::None,
1319            settings: RequestSettings::default(),
1320            pre_request_script: None,
1321            response_handler: None,
1322            save_response: None,
1323        }];
1324
1325        assert!(parsed.errs.is_empty());
1326        assert_eq!(parsed.requests, expected);
1327    }
1328
1329    #[test]
1330    pub fn name_with_at() {
1331        let str = "
1332# @name=test name
1333
1334https://httpbin.org
1335";
1336        let parsed = Parser::parse(str, false);
1337
1338        let expected = vec![model::Request {
1339            name: Some("test name".to_string()),
1340            comments: Vec::new(),
1341            request_line: model::RequestLine {
1342                method: WithDefault::default(),
1343                target: RequestTarget::from("https://httpbin.org"),
1344                http_version: WithDefault::default(),
1345            },
1346            headers: Vec::new(),
1347            body: model::RequestBody::None,
1348            settings: RequestSettings::default(),
1349            pre_request_script: None,
1350            response_handler: None,
1351            save_response: None,
1352        }];
1353
1354        assert!(parsed.errs.is_empty());
1355        assert_eq!(parsed.requests, expected)
1356    }
1357
1358    #[test]
1359    pub fn comment_and_name_tag() {
1360        let str = "
1361### Just a comment
1362## invalid comment but still parsed
1363# @name=actual request name
1364
1365GET https://test.com
1366";
1367        // if there is a ### comment and a @name section use the @name section as name
1368        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
1369        assert!(requests.len() == 1);
1370        let request = requests.remove(0);
1371        assert!(errs.len() == 0);
1372        assert_eq!(request.name, Some("actual request name".to_string()));
1373        assert_eq!(request.comments.len(), 2);
1374        assert_eq!(
1375            request.comments,
1376            vec![
1377                Comment {
1378                    value: "Just a comment".to_string(),
1379                    kind: CommentKind::RequestSeparator
1380                },
1381                Comment {
1382                    value: "# invalid comment but still parsed".to_string(),
1383                    kind: CommentKind::SingleTag
1384                }
1385            ]
1386        );
1387    }
1388
1389    #[test]
1390    pub fn custom_method() {
1391        let str = "
1392# @name=test name
1393
1394CUSTOMVERB https://httpbin.org
1395";
1396        let parsed = Parser::parse(str, false);
1397
1398        let expected = vec![model::Request {
1399            name: Some(String::from("test name")),
1400            comments: Vec::new(),
1401            request_line: model::RequestLine {
1402                method: WithDefault::Some(model::HttpMethod::CUSTOM("CUSTOMVERB".to_string())),
1403                target: RequestTarget::from("https://httpbin.org"),
1404                http_version: WithDefault::default(),
1405            },
1406            headers: Vec::new(),
1407            body: model::RequestBody::None,
1408            settings: RequestSettings::default(),
1409            pre_request_script: None,
1410            response_handler: None,
1411            save_response: None,
1412        }];
1413
1414        assert!(parsed.errs.is_empty());
1415        assert_eq!(parsed.requests, expected);
1416    }
1417
1418    #[test]
1419    pub fn no_body_post() {
1420        let str = "
1421# @name=test name
1422
1423POST https://httpbin.org
1424";
1425        let parsed = Parser::parse(str, false);
1426
1427        let expected = vec![model::Request {
1428            name: Some("test name".to_string()),
1429            comments: Vec::new(),
1430            request_line: model::RequestLine {
1431                method: WithDefault::Some(HttpMethod::POST),
1432                target: RequestTarget::from("https://httpbin.org"),
1433                http_version: WithDefault::default(),
1434            },
1435            headers: Vec::new(),
1436            body: model::RequestBody::None,
1437            settings: RequestSettings::default(),
1438            pre_request_script: None,
1439            response_handler: None,
1440            save_response: None,
1441        }];
1442
1443        assert!(parsed.errs.is_empty());
1444        assert_eq!(parsed.requests, expected);
1445    }
1446
1447    #[test]
1448    pub fn name_with_whitespace() {
1449        let str = "
1450# @name  =  test name    
1451
1452POST https://httpbin.org
1453";
1454        let parsed = Parser::parse(str, false);
1455
1456        let expected = vec![model::Request {
1457            name: Some(String::from("test name")),
1458            comments: Vec::new(),
1459            request_line: model::RequestLine {
1460                method: WithDefault::Some(HttpMethod::POST),
1461                target: RequestTarget::from("https://httpbin.org"),
1462                http_version: WithDefault::default(),
1463            },
1464            headers: Vec::new(),
1465            body: model::RequestBody::None,
1466            settings: RequestSettings::default(),
1467            pre_request_script: None,
1468            response_handler: None,
1469            save_response: None,
1470        }];
1471
1472        // whitespace before or after name should be removed
1473        assert_eq!(parsed.requests[0].name, Some("test name".to_string()));
1474        assert!(parsed.errs.is_empty());
1475        assert_eq!(parsed.requests, expected);
1476    }
1477
1478    #[test]
1479    pub fn multiple_comments() {
1480        let str = "
1481### Comment one
1482### Comment line two    
1483// This comment type is also allowed      
1484# @name  =  test name    
1485
1486POST https://httpbin.org
1487";
1488        let parsed = Parser::parse(str, false);
1489
1490        assert!(parsed.errs.is_empty());
1491        assert_eq!(
1492            parsed.requests[0].get_comment_text(),
1493            Some(
1494                "Comment one\nComment line two    \nThis comment type is also allowed      "
1495                    .to_string()
1496            ),
1497            "parsed: {:?}, {:?}",
1498            parsed.requests,
1499            parsed.errs
1500        );
1501    }
1502
1503    #[test]
1504    pub fn parse_meta_name_line() {
1505        let str = "@name  =  actual request name";
1506        let mut scanner = Scanner::new(str);
1507        let name = Parser::parse_meta_name(&mut scanner)
1508            .expect("can parse name line without error")
1509            .expect("parse returns something");
1510        assert_eq!(name, "actual request name".to_string());
1511    }
1512
1513    #[test]
1514    pub fn request_target_asterisk() {
1515        let FileParseResult { mut requests, errs } = Parser::parse("*", false);
1516        assert_eq!(requests.len(), 1);
1517        let request = requests.remove(0);
1518        assert_eq!(request.request_line.target, RequestTarget::Asterisk);
1519        assert_eq!(errs, vec![]);
1520
1521        // @TODO: is asterisk form only for OPTIONS request?
1522        let FileParseResult { mut requests, errs } = Parser::parse("GET *", false);
1523        assert_eq!(requests.len(), 1);
1524        let request = requests.remove(0);
1525
1526        assert_eq!(request.request_line.target, RequestTarget::Asterisk);
1527        assert_eq!(
1528            request.request_line.method,
1529            WithDefault::Some(HttpMethod::GET)
1530        );
1531        assert_eq!(request.request_line.http_version, WithDefault::default());
1532        assert_eq!(errs, vec![]);
1533
1534        let FileParseResult { mut requests, errs } =
1535            Parser::parse("CUSTOMMETHOD * HTTP/1.1", false);
1536        assert_eq!(requests.len(), 1);
1537        let request = requests.remove(0);
1538
1539        assert_eq!(request.request_line.target, RequestTarget::Asterisk);
1540        assert_eq!(
1541            request.request_line.method,
1542            WithDefault::Some(HttpMethod::CUSTOM(String::from("CUSTOMMETHOD")))
1543        );
1544        assert_eq!(
1545            request.request_line.http_version,
1546            WithDefault::Some(model::HttpVersion { major: 1, minor: 1 })
1547        );
1548        assert_eq!(errs, vec![]);
1549    }
1550
1551    #[test]
1552    pub fn request_target_absolute() {
1553        let FileParseResult { mut requests, errs } =
1554            Parser::parse("https://test.com/api/v1/user?show_all=true&limit=10", false);
1555
1556        assert_eq!(requests.len(), 1);
1557        let request = requests.remove(0);
1558
1559        // only with relative url
1560        let expected_target = RequestTarget::Absolute {
1561            uri: "https://test.com/api/v1/user?show_all=true&limit=10".to_string(),
1562        };
1563        assert_eq!(request.request_line.target, expected_target);
1564
1565        match request.request_line.target {
1566            RequestTarget::Absolute { ref uri } => {
1567                assert_eq!(uri, "https://test.com/api/v1/user?show_all=true&limit=10");
1568            }
1569            _ => panic!("not expected target found"),
1570        }
1571
1572        assert!(request.request_line.target.has_scheme());
1573        assert_eq!(errs, vec![]);
1574
1575        // method and URL
1576        let FileParseResult { requests, errs } = Parser::parse(
1577            "GET https://test.com/api/v1/user?show_all=true&limit=10",
1578            false,
1579        );
1580        assert_eq!(requests.len(), 1);
1581        let request = &requests[0];
1582        assert_eq!(request.request_line.target, expected_target);
1583        assert_eq!(
1584            request.request_line.method,
1585            WithDefault::Some(HttpMethod::GET)
1586        );
1587        assert_eq!(request.request_line.http_version, WithDefault::default());
1588        assert_eq!(errs, vec![]);
1589
1590        // method and URL and http version
1591        let FileParseResult { mut requests, errs } = Parser::parse(
1592            "GET https://test.com/api/v1/user?show_all=true&limit=10    HTTP/1.1",
1593            false,
1594        );
1595        assert_eq!(requests.len(), 1);
1596        let request = requests.remove(0);
1597        assert_eq!(request.request_line.target, expected_target);
1598        assert_eq!(
1599            request.request_line.method,
1600            WithDefault::Some(HttpMethod::GET)
1601        );
1602        assert_eq!(
1603            request.request_line.http_version,
1604            WithDefault::Some(model::HttpVersion { major: 1, minor: 1 })
1605        );
1606        assert_eq!(errs, vec![]);
1607    }
1608
1609    #[test]
1610    pub fn request_target_no_scheme_with_host_no_path() {
1611        let FileParseResult { mut requests, errs } = Parser::parse("test.com", false);
1612        assert_eq!(errs, vec![]);
1613        assert_eq!(requests.len(), 1);
1614        let request = requests.remove(0);
1615        match request.request_line.target {
1616            RequestTarget::Absolute { ref uri } => {
1617                assert_eq!(uri, "test.com");
1618            }
1619            kind => panic!("!request target is not absolute kind, it is: {:?}", kind),
1620        }
1621    }
1622
1623    #[test]
1624    pub fn request_target_no_scheme_with_host_and_path() {
1625        let FileParseResult { mut requests, errs } = Parser::parse("test.com/api/v1/test", false);
1626        assert_eq!(errs, vec![]);
1627        assert_eq!(requests.len(), 1);
1628        let request = requests.remove(0);
1629        match request.request_line.target {
1630            RequestTarget::Absolute { ref uri } => {
1631                // @TODO: with uri parser we cannot have
1632                // authority and path without a scheme, add http as default in this case if no
1633                // scheme is present
1634
1635                assert_eq!(uri, "test.com/api/v1/test");
1636            }
1637            kind => panic!("!request target is not absolute kind, it is: {:?}", kind),
1638        }
1639    }
1640
1641    #[test]
1642    pub fn request_target_relative() {
1643        let FileParseResult { mut requests, errs } =
1644            Parser::parse("/api/v1/user?show_all=true&limit=10", false);
1645        assert_eq!(requests.len(), 1);
1646        let request = requests.remove(0);
1647
1648        // only with relative url
1649        let expected_target = RequestTarget::RelativeOrigin {
1650            uri: "/api/v1/user?show_all=true&limit=10".to_string(),
1651        };
1652        assert_eq!(request.request_line.target, expected_target);
1653
1654        match request.request_line.target {
1655            RequestTarget::RelativeOrigin { ref uri } => {
1656                assert_eq!(uri, "/api/v1/user?show_all=true&limit=10");
1657            }
1658            _ => panic!("not expected target found"),
1659        }
1660
1661        assert!(!request.request_line.target.has_scheme());
1662        assert_eq!(errs, vec![]);
1663
1664        // method and URL
1665        let FileParseResult { mut requests, errs } =
1666            Parser::parse("GET /api/v1/user?show_all=true&limit=10", false);
1667        assert_eq!(requests.len(), 1);
1668        let request = requests.remove(0);
1669        assert_eq!(request.request_line.target, expected_target);
1670        assert_eq!(
1671            request.request_line.method,
1672            WithDefault::Some(HttpMethod::GET)
1673        );
1674        assert_eq!(request.request_line.http_version, WithDefault::default());
1675        assert_eq!(errs, vec![]);
1676
1677        // method and URL and http version
1678        let FileParseResult { mut requests, errs } =
1679            Parser::parse("GET /api/v1/user?show_all=true&limit=10    HTTP/1.1", false);
1680        assert_eq!(requests.len(), 1);
1681        let request = requests.remove(0);
1682        assert_eq!(request.request_line.target, expected_target);
1683        assert_eq!(
1684            request.request_line.method,
1685            WithDefault::Some(HttpMethod::GET)
1686        );
1687        assert_eq!(
1688            request.request_line.http_version,
1689            WithDefault::Some(model::HttpVersion { major: 1, minor: 1 })
1690        );
1691        assert_eq!(errs, vec![]);
1692    }
1693
1694    #[test]
1695    pub fn validate_http_version() {
1696        let version = model::HttpVersion::from_str("HTTP/1.1").expect("Version 1.1 to be valid");
1697        assert_eq!(version, model::HttpVersion { major: 1, minor: 1 });
1698
1699        let version = model::HttpVersion::from_str("HTTP/1.2").expect("Version 1.2 to be valid");
1700        assert_eq!(version, model::HttpVersion { major: 1, minor: 2 });
1701
1702        let version = model::HttpVersion::from_str("HTTP/2.0").expect("Version 2.0 to be valid");
1703        assert_eq!(version, model::HttpVersion { major: 2, minor: 0 });
1704
1705        let version = model::HttpVersion::from_str("HTTP/2.1").expect("Version 2.1 to be valid");
1706        assert_eq!(version, model::HttpVersion { major: 2, minor: 1 });
1707
1708        assert!(model::HttpVersion::from_str("invalid").is_err());
1709    }
1710
1711    #[test]
1712    pub fn request_target_multiline() {
1713        let str = r#####"
1714GET https://test.com:8080
1715    /get
1716    /html
1717    ?id=123
1718    &value=test
1719
1720        "#####;
1721        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
1722        assert_eq!(errs, vec![]);
1723        assert_eq!(requests.len(), 1);
1724        let request = requests.remove(0);
1725        assert_eq!(
1726            request.request_line.target,
1727            RequestTarget::Absolute {
1728                uri: "https://test.com:8080/get/html?id=123&value=test".to_owned()
1729            }
1730        );
1731        assert_eq!(request.request_line.http_version, WithDefault::default());
1732        assert_eq!(
1733            request.request_line.method,
1734            WithDefault::Some(HttpMethod::GET)
1735        );
1736    }
1737
1738    #[test]
1739    pub fn request_target_multiline_no_method() {
1740        let str = r#####"
1741https://test.com:8080
1742    /get
1743    /html
1744    ?id=123
1745    &value=test
1746
1747        "#####;
1748        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
1749        assert_eq!(errs, vec![]);
1750        assert_eq!(requests.len(), 1);
1751        let request = requests.remove(0);
1752        assert_eq!(
1753            request.request_line.target,
1754            RequestTarget::Absolute {
1755                uri: "https://test.com:8080/get/html?id=123&value=test".to_owned()
1756            }
1757        );
1758        assert_eq!(request.request_line.http_version, WithDefault::default());
1759        assert_eq!(request.request_line.method, WithDefault::default());
1760    }
1761
1762    #[test]
1763    pub fn request_target_multiline_with_version() {
1764        let str = r#####"
1765GET https://test.com:8080
1766    /get
1767    /html
1768    ?id=123
1769    &value=test HTTP/2.1
1770
1771        "#####;
1772        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
1773        assert_eq!(errs, vec![]);
1774        assert_eq!(requests.len(), 1);
1775        let request = requests.remove(0);
1776        assert_eq!(
1777            request.request_line.target,
1778            RequestTarget::Absolute {
1779                uri: "https://test.com:8080/get/html?id=123&value=test".to_owned()
1780            }
1781        );
1782        assert_eq!(
1783            request.request_line.http_version,
1784            WithDefault::Some(HttpVersion { major: 2, minor: 1 })
1785        );
1786        assert_eq!(
1787            request.request_line.method,
1788            WithDefault::Some(HttpMethod::GET)
1789        );
1790    }
1791
1792    #[test]
1793    pub fn parse_simple_headers() {
1794        let str = "Key1: Value1
1795Key2: Value2
1796Key3: Value3
1797";
1798        let mut scanner = Scanner::new(str);
1799        let parsed = Parser::parse_headers(&mut scanner);
1800
1801        let parsed = parsed.expect("No error for simple headers");
1802
1803        assert_eq!(parsed.len(), 3);
1804        assert_eq!(parsed[0], Header::new("Key1", "Value1"));
1805        assert_eq!(parsed[1], Header::new("Key2", "Value2"));
1806        assert_eq!(parsed[2], Header::new("Key3", "Value3"));
1807    }
1808
1809    #[test]
1810    pub fn parse_headers_with_colon() {
1811        let str = r###"Host: localhost:8080
1812Custom: ::::::
1813
1814        "###;
1815        let mut scanner = Scanner::new(str);
1816        let parsed = Parser::parse_headers(&mut scanner).unwrap();
1817
1818        assert_eq!(parsed.len(), 2);
1819        assert_eq!(parsed[0], Header::new("Host", "localhost:8080"));
1820        assert_eq!(parsed[1], Header::new("Custom", "::::::"));
1821    }
1822
1823    #[test]
1824    pub fn parse_with_multipart_body_file() {
1825        let str = r####"
1826# With Multipart Body
1827POST https://test.com/multipart
1828Content-Type: multipart/form-data; boundary="--test_boundary"
1829
1830----test_boundary
1831Content-Disposition: form-data; name="part1_name"
1832
1833< path/to/file
1834----test_boundary--
1835"####;
1836
1837        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
1838        assert_eq!(errs, vec![]);
1839        assert_eq!(requests.len(), 1);
1840        let request = requests.remove(0);
1841
1842        assert_eq!(
1843            request.headers,
1844            vec![Header::new(
1845                "Content-Type",
1846                "multipart/form-data; boundary=\"--test_boundary\""
1847            )]
1848        );
1849
1850        assert_eq!(
1851            request.body,
1852            model::RequestBody::Multipart {
1853                boundary: "--test_boundary".to_string(),
1854                parts: vec![Multipart {
1855                    disposition: DispositionField::new_with_filename("part1_name", None::<String>),
1856                    data: DataSource::FromFilepath("path/to/file".to_string()),
1857                    headers: vec![]
1858                }]
1859            }
1860        )
1861    }
1862
1863    #[test]
1864    pub fn parse_with_multipart_body_text() {
1865        let str = r####"
1866# With Multipart Body
1867POST https://test.com/multipart
1868Content-Type: multipart/form-data; boundary="--test.?)()test"
1869
1870----test.?)()test
1871Content-Disposition: form-data; name="text"
1872
1873some text
1874
1875----test.?)()test
1876Content-Disposition: form-data; name="text"
1877
1878more content
1879
1880
1881----test.?)()test--
1882"####;
1883
1884        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
1885        assert_eq!(errs, vec![]);
1886        assert_eq!(requests.len(), 1);
1887        let request = requests.remove(0);
1888
1889        assert_eq!(
1890            request.headers,
1891            vec![Header::new(
1892                "Content-Type",
1893                "multipart/form-data; boundary=\"--test.?)()test\""
1894            )]
1895        );
1896
1897        assert_eq!(
1898            request.body,
1899            model::RequestBody::Multipart {
1900                boundary: "--test.?)()test".to_string(),
1901                parts: vec![
1902                    Multipart {
1903                        disposition: DispositionField::new("text"),
1904                        headers: vec![],
1905                        data: DataSource::Raw("some text\n".to_string()),
1906                    },
1907                    Multipart {
1908                        disposition: DispositionField::new("text"),
1909                        headers: vec![],
1910                        data: DataSource::Raw("more content\n\n".to_string()),
1911                    }
1912                ]
1913            }
1914        )
1915    }
1916
1917    #[test]
1918    pub fn parse_multipart_with_content_types() {
1919        let str = r#####"
1920### Send a form with the text and file fields
1921POST https://httpbin.org/post
1922Content-Type: multipart/form-data; boundary=WebAppBoundary
1923
1924--WebAppBoundary
1925Content-Disposition: form-data; name="element-name"
1926Content-Type: text/plain
1927
1928Name
1929--WebAppBoundary
1930Content-Disposition: form-data; name="data"; filename="data.json"
1931Content-Type: application/json
1932
1933< ./request-form-data.json
1934--WebAppBoundary--
1935        "#####;
1936
1937        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
1938        assert_eq!(errs, vec![]);
1939        assert_eq!(requests.len(), 1);
1940
1941        let request = requests.remove(0);
1942
1943        assert_eq!(
1944            request.headers,
1945            vec![Header::new(
1946                "Content-Type",
1947                "multipart/form-data; boundary=WebAppBoundary"
1948            )]
1949        );
1950
1951        assert_eq!(
1952            request.body,
1953            model::RequestBody::Multipart {
1954                boundary: "WebAppBoundary".to_string(),
1955                parts: vec![
1956                    Multipart {
1957                        data: DataSource::Raw("Name".to_string()),
1958                        disposition: DispositionField::new("element-name"),
1959                        headers: vec![Header {
1960                            key: "Content-Type".to_string(),
1961                            value: "text/plain".to_string()
1962                        }]
1963                    },
1964                    Multipart {
1965                        data: DataSource::FromFilepath("./request-form-data.json".to_string()),
1966                        disposition: DispositionField::new_with_filename("data", Some("data.json")),
1967                        headers: vec![Header {
1968                            key: "Content-Type".to_string(),
1969                            value: "application/json".to_string()
1970                        }]
1971                    }
1972                ]
1973            }
1974        )
1975    }
1976
1977    #[test]
1978    pub fn parse_multipart_binary() {
1979        let str = r#####"
1980POST /upload HTTP/1.1
1981Host: localhost:8080
1982Content-Type: multipart/form-data; boundary=/////////////////////////////
1983Content-Length: 676
1984
1985--/////////////////////////////
1986Content-Disposition: form-data; name="file"; filename="binaryfile.tar.gz"
1987Content-Type: application/x-gzip
1988Content-Transfer-Encoding: base64
1989
1990H4sIAGiNIU8AA+3R0W6CMBQGYK59iobLZantRDG73osUOGqnFNJWM2N897UghG1ZdmWWLf93U/jP4bRAq8q92hJ/dY1J7kQEqyyLq8yXYrp2ltkqkTKXYiEykYc++ZTLVcLEvQ40dXReWcYSV1pdnL/v+6n+R11mjKVG1ZQ+s3TT2FpXqjhQ+hjzE1mnGxNLkgu+7tOKWjIVmVKTC6XL9ZaeXj4VQhwKWzL+cI4zwgQuuhkh3mhTad/Hkssh3im3027X54JnQ360R/M19OT8kC7SEN7Ooi2VvrEfznHQRWzl83gxttZKmzGehzPRW/+W8X+3fvL8sFet9sS6m3EIma02071MU3Uf9KHrmV1/+y8DAAAAAAAAAAAAAAAAAAAAAMB/9A6txIuJACgAAA==
1991--/////////////////////////////--
1992        "#####;
1993
1994        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
1995        assert_eq!(errs, vec![]);
1996        assert_eq!(requests.len(), 1);
1997        let request = requests.remove(0);
1998
1999        assert_eq!(
2000            request.headers,
2001            vec![
2002                Header::new("Host", "localhost:8080"),
2003                Header::new(
2004                    "Content-Type",
2005                    r#"multipart/form-data; boundary=/////////////////////////////"#
2006                ),
2007                Header::new("Content-Length", "676")
2008            ]
2009        );
2010
2011        // @TODO check content
2012        assert_eq!(
2013            request.body,
2014            model::RequestBody::Multipart {
2015                boundary: r#"/////////////////////////////"#.to_string(),
2016                parts: vec![model::Multipart {
2017                    disposition: DispositionField::new_with_filename("file", Some("binaryfile.tar.gz")),
2018                    headers: vec![
2019                        Header {
2020                            key: "Content-Type".to_string(),
2021                            value: "application/x-gzip".to_string()
2022                        },
2023                        Header {
2024                            key: "Content-Transfer-Encoding".to_string(),
2025                            value: "base64".to_string()
2026                        }
2027                    ],
2028                    data: DataSource::Raw("H4sIAGiNIU8AA+3R0W6CMBQGYK59iobLZantRDG73osUOGqnFNJWM2N897UghG1ZdmWWLf93U/jP4bRAq8q92hJ/dY1J7kQEqyyLq8yXYrp2ltkqkTKXYiEykYc++ZTLVcLEvQ40dXReWcYSV1pdnL/v+6n+R11mjKVG1ZQ+s3TT2FpXqjhQ+hjzE1mnGxNLkgu+7tOKWjIVmVKTC6XL9ZaeXj4VQhwKWzL+cI4zwgQuuhkh3mhTad/Hkssh3im3027X54JnQ360R/M19OT8kC7SEN7Ooi2VvrEfznHQRWzl83gxttZKmzGehzPRW/+W8X+3fvL8sFet9sS6m3EIma02071MU3Uf9KHrmV1/+y8DAAAAAAAAAAAAAAAAAAAAAMB/9A6txIuJACgAAA==".to_string())
2029                }]
2030            }
2031        )
2032    }
2033
2034    #[test]
2035    pub fn parse_json_body() {
2036        let str = r#####"
2037GET http://localhost/api/json/get?id=12345
2038Authorization: Basic dev-user dev-password
2039Content-Type: application/json
2040
2041{
2042    "key": "my-dev-value"
2043}"#####;
2044
2045        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
2046        assert_eq!(errs, vec![]);
2047        assert_eq!(requests.len(), 1);
2048
2049        let request = requests.remove(0);
2050
2051        assert_eq!(
2052            request.headers,
2053            vec![
2054                Header::new("Authorization", r#"Basic dev-user dev-password"#),
2055                Header::new("Content-Type", "application/json")
2056            ]
2057        );
2058
2059        assert_eq!(
2060            request.body,
2061            model::RequestBody::Raw {
2062                data: DataSource::Raw(
2063                    r#"{
2064    "key": "my-dev-value"
2065}"#
2066                    .to_string()
2067                )
2068            }
2069        )
2070    }
2071
2072    #[test]
2073    pub fn parse_json_body_fileinput() {
2074        let str = r#####"
2075POST http://example.com/api/add
2076Content-Type: application/json
2077
2078< ./input.json
2079
2080        "#####;
2081
2082        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
2083        assert_eq!(errs, vec![]);
2084        assert_eq!(requests.len(), 1);
2085
2086        let request = requests.remove(0);
2087
2088        assert_eq!(
2089            request.headers,
2090            vec![Header::new("Content-Type", "application/json")]
2091        );
2092
2093        // @TODO check content
2094        assert_eq!(
2095            request.body,
2096            model::RequestBody::Raw {
2097                data: DataSource::FromFilepath("./input.json".to_string())
2098            }
2099        )
2100    }
2101
2102    #[test]
2103    pub fn parse_url_form_encoded_end_of_file() {
2104        let str = r####"# @name=Create Checkout Session
2105POST {{base_url}}/create_checkout_session?a=aa
2106Content-Type: application/x-www-form-urlencoded
2107
2108abc=def&ghi=jkl"####;
2109        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
2110        assert_eq!(errs, vec![]);
2111        assert_eq!(requests.len(), 1);
2112        let request = requests.remove(0);
2113
2114        assert_eq!(
2115            request.headers,
2116            vec![Header::new(
2117                "Content-Type",
2118                "application/x-www-form-urlencoded"
2119            )]
2120        );
2121
2122        assert_eq!(
2123            request.body,
2124            RequestBody::UrlEncoded {
2125                url_encoded_params: vec![
2126                    UrlEncodedParam::new("abc", "def"),
2127                    UrlEncodedParam::new("ghi", "jkl"),
2128                ]
2129            }
2130        )
2131    }
2132
2133    #[test]
2134    pub fn parse_url_form_encoded() {
2135        let str = r####"
2136POST https://test.com/formEncoded
2137Content-Type: application/x-www-form-urlencoded
2138
2139firstKey=firstValue&secondKey=secondValue&empty=
2140"####;
2141
2142        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
2143        assert_eq!(errs, vec![]);
2144        assert_eq!(requests.len(), 1);
2145        let request = requests.remove(0);
2146
2147        assert_eq!(
2148            request.headers,
2149            vec![Header::new(
2150                "Content-Type",
2151                "application/x-www-form-urlencoded"
2152            )]
2153        );
2154
2155        assert_eq!(
2156            request.body,
2157            RequestBody::UrlEncoded {
2158                url_encoded_params: vec![
2159                    UrlEncodedParam::new("firstKey", "firstValue"),
2160                    UrlEncodedParam::new("secondKey", "secondValue"),
2161                    UrlEncodedParam::new("empty", ""),
2162                ]
2163            }
2164        )
2165    }
2166
2167    #[test]
2168    pub fn parse_multiple_requests() {
2169        let str = r#####"
2170POST http://example.com/api/add
2171Content-Type: application/json
2172
2173< ./input.json
2174###
2175
2176GET https://example.com/first
2177###
2178GET https://example.com/second
2179
2180
2181###
2182        "#####;
2183
2184        let FileParseResult { requests, errs } = dbg!(Parser::parse(str, false));
2185        println!("errs: {:?}", errs);
2186        assert_eq!(errs.len(), 1);
2187        assert_eq!(requests.len(), 3);
2188
2189        // @TODO check content
2190        assert_eq!(
2191            requests,
2192            vec![
2193                model::Request {
2194                    name: None,
2195                    comments: vec![],
2196                    headers: vec![Header {
2197                        key: "Content-Type".to_string(),
2198                        value: "application/json".to_string()
2199                    }],
2200                    body: model::RequestBody::Raw {
2201                        data: DataSource::FromFilepath("./input.json".to_string())
2202                    },
2203                    request_line: model::RequestLine {
2204                        http_version: WithDefault::default(),
2205                        method: WithDefault::Some(HttpMethod::POST),
2206                        target: model::RequestTarget::Absolute {
2207                            uri: "http://example.com/api/add".to_string()
2208                        }
2209                    },
2210                    settings: RequestSettings::default(),
2211                    pre_request_script: None,
2212                    response_handler: None,
2213                    save_response: None,
2214                },
2215                model::Request {
2216                    name: None,
2217                    comments: vec![],
2218                    headers: vec![],
2219                    body: model::RequestBody::None,
2220                    request_line: model::RequestLine {
2221                        http_version: WithDefault::default(),
2222                        method: WithDefault::Some(HttpMethod::GET),
2223                        target: model::RequestTarget::Absolute {
2224                            uri: "https://example.com/first".to_string()
2225                        }
2226                    },
2227                    settings: RequestSettings::default(),
2228                    pre_request_script: None,
2229                    response_handler: None,
2230                    save_response: None,
2231                },
2232                model::Request {
2233                    name: None,
2234                    comments: vec![],
2235                    headers: vec![],
2236                    body: model::RequestBody::None,
2237                    request_line: model::RequestLine {
2238                        http_version: WithDefault::default(),
2239                        method: WithDefault::Some(HttpMethod::GET),
2240                        target: model::RequestTarget::Absolute {
2241                            uri: "https://example.com/second".to_string()
2242                        }
2243                    },
2244                    settings: RequestSettings::default(),
2245                    pre_request_script: None,
2246                    response_handler: None,
2247                    save_response: None
2248                }
2249            ],
2250        );
2251    }
2252
2253    #[test]
2254    pub fn parse_meta_directives() {
2255        let str = r#####"
2256### The Request
2257# @no-redirect
2258// @no-log
2259// @name= RequestName
2260# @no-cookie-jar
2261GET https://httpbin.org
2262"#####;
2263        let FileParseResult { requests, errs } = Parser::parse(str, false);
2264        assert_eq!(errs, vec![]);
2265        assert_eq!(requests.len(), 1);
2266        assert_eq!(
2267            requests[0],
2268            Request {
2269                name: Some("RequestName".to_string()),
2270                headers: vec![],
2271                comments: vec![Comment {
2272                    value: "The Request".to_string(),
2273                    kind: CommentKind::RequestSeparator
2274                }],
2275                settings: RequestSettings {
2276                    no_redirect: Some(true),
2277                    no_log: Some(true),
2278                    no_cookie_jar: Some(true),
2279                },
2280                request_line: RequestLine {
2281                    method: WithDefault::Some(HttpMethod::GET),
2282                    target: RequestTarget::from("https://httpbin.org"),
2283                    http_version: WithDefault::default()
2284                },
2285                body: model::RequestBody::None,
2286                pre_request_script: None,
2287                response_handler: None,
2288                save_response: None
2289            }
2290        );
2291    }
2292
2293    #[test]
2294    pub fn parse_pre_request_script_single_line() {
2295        let str = r#####"
2296### Request
2297< {%     request.variables.set("firstname", "John") %}
2298// @no-log
2299GET https://httpbin.org
2300"#####;
2301        let FileParseResult { requests, errs } = Parser::parse(str, false);
2302        assert_eq!(errs, vec![]);
2303        assert_eq!(requests.len(), 1);
2304        assert_eq!(
2305            requests[0],
2306            Request {
2307                name: Some("Request".to_string()),
2308                headers: vec![],
2309                comments: vec![],
2310                settings: RequestSettings {
2311                    no_redirect: Some(false),
2312                    no_log: Some(true),
2313                    no_cookie_jar: Some(false),
2314                },
2315                request_line: RequestLine {
2316                    method: WithDefault::Some(HttpMethod::GET),
2317                    target: RequestTarget::from("https://httpbin.org"),
2318                    http_version: WithDefault::default()
2319                },
2320                body: model::RequestBody::None,
2321                pre_request_script: Some(model::PreRequestScript::Script(
2322                    r#"     request.variables.set("firstname", "John") "#.to_string()
2323                )),
2324                response_handler: None,
2325                save_response: None
2326            }
2327        );
2328    }
2329
2330    #[test]
2331    pub fn parse_pre_request_script_multiple_lines() {
2332        let str = r#####"
2333### Request
2334< {%
2335 const signature = crypto.hmac.sha256()
2336        .withTextSecret(request.environment.get("secret")) // get variable from http-client.private.env.json
2337        .updateWithText(request.body.tryGetSubstituted())
2338        .digest().toHex();
2339    request.variables.set("signature", signature)
2340
2341    const hash = crypto.sha256()
2342        .updateWithText(request.body.tryGetSubstituted())
2343        .digest().toHex();
2344    request.variables.set("hash", hash)
2345%}
2346// @no-log
2347GET https://httpbin.org
2348"#####;
2349
2350        let pre_request_script = r#####"
2351 const signature = crypto.hmac.sha256()
2352        .withTextSecret(request.environment.get("secret")) // get variable from http-client.private.env.json
2353        .updateWithText(request.body.tryGetSubstituted())
2354        .digest().toHex();
2355    request.variables.set("signature", signature)
2356
2357    const hash = crypto.sha256()
2358        .updateWithText(request.body.tryGetSubstituted())
2359        .digest().toHex();
2360    request.variables.set("hash", hash)
2361"#####;
2362
2363        let FileParseResult { requests, errs } = Parser::parse(str, false);
2364        assert_eq!(errs, vec![]);
2365        assert_eq!(requests.len(), 1);
2366        assert_eq!(
2367            requests[0],
2368            Request {
2369                name: Some("Request".to_string()),
2370                headers: vec![],
2371                comments: vec![],
2372                settings: RequestSettings {
2373                    no_redirect: Some(false),
2374                    no_log: Some(true),
2375                    no_cookie_jar: Some(false),
2376                },
2377                request_line: RequestLine {
2378                    method: WithDefault::Some(HttpMethod::GET),
2379                    target: RequestTarget::from("https://httpbin.org"),
2380                    http_version: WithDefault::default()
2381                },
2382                body: model::RequestBody::None,
2383                pre_request_script: Some(model::PreRequestScript::Script(
2384                    pre_request_script.to_string()
2385                )),
2386                response_handler: None,
2387                save_response: None,
2388            }
2389        );
2390    }
2391
2392    #[test]
2393    pub fn parse_handler_script_single_line() {
2394        let str = r#####"
2395### Request
2396// @no-log
2397GET https://httpbin.org
2398
2399> {% client.global.set("my_cookie", response.headers.valuesOf("Set-Cookie")[0]); %} 
2400"#####;
2401
2402        let response_handler_script = r#####" client.global.set("my_cookie", response.headers.valuesOf("Set-Cookie")[0]); "#####;
2403
2404        let FileParseResult { requests, errs } = Parser::parse(str, false);
2405        assert_eq!(errs, vec![]);
2406        assert_eq!(requests.len(), 1);
2407        assert_eq!(
2408            requests[0],
2409            Request {
2410                name: Some("Request".to_string()),
2411                headers: vec![],
2412                comments: vec![],
2413                settings: RequestSettings {
2414                    no_redirect: Some(false),
2415                    no_log: Some(true),
2416                    no_cookie_jar: Some(false),
2417                },
2418                request_line: RequestLine {
2419                    method: WithDefault::Some(HttpMethod::GET),
2420                    target: RequestTarget::from("https://httpbin.org"),
2421                    http_version: WithDefault::default()
2422                },
2423                body: model::RequestBody::None,
2424                pre_request_script: None,
2425                response_handler: Some(ResponseHandler::Script(
2426                    response_handler_script.to_string()
2427                )),
2428                save_response: None
2429            }
2430        );
2431    }
2432    #[test]
2433    pub fn parse_handler_script_multiple_lines() {
2434        let str = r#####"
2435### Request
2436// @no-log
2437GET https://httpbin.org
2438
2439> {%
2440    client.global.set("my_cookie", response.headers.valuesOf("Set-Cookie")[0]);
2441    client.global.set("my_cookie_2", response.headers.valuesOf("Set-Cookie")[0]);
2442%} 
2443"#####;
2444
2445        let response_handler_script = r#####"
2446    client.global.set("my_cookie", response.headers.valuesOf("Set-Cookie")[0]);
2447    client.global.set("my_cookie_2", response.headers.valuesOf("Set-Cookie")[0]);
2448"#####;
2449
2450        let FileParseResult { requests, errs } = Parser::parse(str, false);
2451        assert_eq!(errs, vec![]);
2452        assert_eq!(requests.len(), 1);
2453        assert_eq!(
2454            requests[0],
2455            Request {
2456                name: Some("Request".to_string()),
2457                headers: vec![],
2458                comments: vec![],
2459                settings: RequestSettings {
2460                    no_redirect: Some(false),
2461                    no_log: Some(true),
2462                    no_cookie_jar: Some(false),
2463                },
2464                request_line: RequestLine {
2465                    method: WithDefault::Some(HttpMethod::GET),
2466                    target: RequestTarget::from("https://httpbin.org"),
2467                    http_version: WithDefault::default()
2468                },
2469                body: model::RequestBody::None,
2470                pre_request_script: None,
2471                response_handler: Some(ResponseHandler::Script(
2472                    response_handler_script.to_string()
2473                )),
2474                save_response: None
2475            }
2476        );
2477    }
2478
2479    #[test]
2480    pub fn has_valid_extension() {
2481        // ok
2482        assert!(Parser::has_valid_extension(&"test.rest"));
2483        assert!(Parser::has_valid_extension(&"rest.http"));
2484
2485        assert!(Parser::has_valid_extension(&"C:\\folder\\test.rest"));
2486        assert!(Parser::has_valid_extension(&"/home/user/test.rest"));
2487
2488        assert!(Parser::has_valid_extension(&std::path::Path::new(
2489            "test.rest"
2490        )));
2491
2492        assert!(Parser::has_valid_extension(&std::path::Path::new(
2493            "test.http"
2494        )));
2495
2496        assert!(Parser::has_valid_extension(&std::path::Path::new(
2497            "C:\\folder\\test.rest"
2498        )));
2499
2500        assert!(Parser::has_valid_extension(&std::path::Path::new(
2501            "/home/usr/folder/test.rest"
2502        )));
2503
2504        // nok
2505        assert!(!Parser::has_valid_extension(&"test"));
2506        assert!(!Parser::has_valid_extension(&"/home/user/test"));
2507        assert!(!Parser::has_valid_extension(&""));
2508    }
2509
2510    #[test]
2511    // https://www.rfc-editor.org/rfc/rfc2046#section-5.1.1
2512    pub fn is_multipart_boundary_valid() {
2513        // at least one character is required
2514        let boundary = "";
2515        assert_eq!(Parser::is_multipart_boundary_valid(boundary).is_err(), true);
2516
2517        // no more than 70 characters
2518        let boundary = "a".repeat(71);
2519        assert_eq!(
2520            Parser::is_multipart_boundary_valid(&boundary).is_err(),
2521            true
2522        );
2523
2524        // at least one character is required
2525        let boundary = "a";
2526
2527        assert_eq!(
2528            Parser::is_multipart_boundary_valid(&boundary).is_err(),
2529            false
2530        );
2531
2532        // up to 70 characters is ok
2533        let boundary = "a".repeat(70);
2534        assert_eq!(
2535            Parser::is_multipart_boundary_valid(&boundary).is_err(),
2536            false
2537        );
2538
2539        // no spaces within allowed
2540        let boundary = "a b";
2541        assert_eq!(
2542            Parser::is_multipart_boundary_valid(&boundary).is_err(),
2543            true
2544        );
2545
2546        // these characters are allowed
2547        let boundary = "0123456789abcdefghijklmnopqrstuvwyxz";
2548        assert_eq!(
2549            Parser::is_multipart_boundary_valid(&boundary).is_err(),
2550            false
2551        );
2552
2553        let boundary = "ABCDEFGHIJKLMNOPQRSTUVWXYZ'()+_,-./:=?";
2554        assert_eq!(
2555            Parser::is_multipart_boundary_valid(&boundary).is_err(),
2556            false
2557        );
2558    }
2559
2560    #[test]
2561    pub fn parse_with_redirect_overwrite_response() {
2562        let str = r###"# @name=New Request
2563GET https://httpbin.org/get
2564
2565>>! test.txt"###;
2566
2567        let FileParseResult { requests, errs } = Parser::parse(str, false);
2568        assert_eq!(errs, vec![]);
2569        assert_eq!(requests.len(), 1);
2570        assert_eq!(
2571            requests[0],
2572            Request {
2573                name: Some("New Request".to_string()),
2574                request_line: RequestLine {
2575                    method: WithDefault::Some(HttpMethod::GET),
2576                    target: RequestTarget::from("https://httpbin.org/get"),
2577                    http_version: WithDefault::default()
2578                },
2579                save_response: Some(SaveResponse::RewriteFile(std::path::PathBuf::from(
2580                    "test.txt"
2581                ))),
2582
2583                ..Default::default()
2584            }
2585        );
2586    }
2587
2588    #[test]
2589    pub fn parse_with_redirect_new_file_response() {
2590        let str = r###"# @name=New Request
2591GET https://httpbin.org/get
2592
2593>> test.txt"###;
2594
2595        let FileParseResult { requests, errs } = Parser::parse(str, false);
2596        assert_eq!(errs, vec![]);
2597        assert_eq!(requests.len(), 1);
2598        assert_eq!(
2599            requests[0],
2600            Request {
2601                name: Some("New Request".to_string()),
2602                request_line: RequestLine {
2603                    method: WithDefault::Some(HttpMethod::GET),
2604                    target: RequestTarget::from("https://httpbin.org/get"),
2605                    http_version: WithDefault::default()
2606                },
2607                save_response: Some(SaveResponse::NewFileIfExists(std::path::PathBuf::from(
2608                    "test.txt"
2609                ))),
2610
2611                ..Default::default()
2612            }
2613        );
2614    }
2615
2616    #[test]
2617    /// If no boundary is given use default boundary '--boundary--'
2618    pub fn parse_multipart_no_boundary() {
2619        let str = r####"# @name=New Request
2620GET https://httpbin.org/{{abc}}
2621Content-Type: multipart/form-data
2622
2623--boundary--
2624
2625>>! test.txt"####;
2626
2627        let FileParseResult { requests, errs } = Parser::parse(str, false);
2628        // should have one error warning that no boundary was given
2629        assert_eq!(errs.len(), 1);
2630        assert!(matches!(
2631            errs[0].details[0].error,
2632            ParseError::MissingMultipartHeaderBoundaryDefinition(_)
2633        ));
2634        //assert_eq!(errs, vec![]);
2635        assert_eq!(requests.len(), 0);
2636        assert_eq!(
2637            Into::<Request>::into(errs[0].partial_request.clone()),
2638            Request {
2639                name: Some("New Request".to_string()),
2640                request_line: RequestLine {
2641                    method: WithDefault::Some(HttpMethod::GET),
2642                    target: RequestTarget::from("https://httpbin.org/{{abc}}"),
2643                    http_version: WithDefault::default()
2644                },
2645                headers: vec![Header::new("Content-Type", "multipart/form-data")],
2646                body: RequestBody::Multipart {
2647                    boundary: "--boundary--".to_string(),
2648                    parts: vec![]
2649                },
2650                save_response: Some(SaveResponse::RewriteFile(std::path::PathBuf::from(
2651                    "test.txt"
2652                ))),
2653
2654                ..Default::default()
2655            }
2656        );
2657    }
2658
2659    #[test]
2660    pub fn parse_multipart_single_boundary_no_filename() {
2661        let str = r###"# @name=New Request
2662GET https://httpbin.org/{{abc}}
2663Content-Type: multipart/form-data; boundary="--boundary--"
2664
2665----boundary--
2666Content-Disposition: form-data; name=""
2667
2668
2669----boundary----"###;
2670
2671        let FileParseResult { requests, errs } = Parser::parse(str, false);
2672        // one error allowed, name should not be empty of content-disposition inside a multipart
2673        assert_eq!(errs.len(), 1);
2674        //assert_eq!(errs, vec![]);
2675        assert_eq!(requests.len(), 0);
2676        assert_eq!(
2677            Into::<Request>::into(errs[0].partial_request.clone()),
2678            Request {
2679                name: Some("New Request".to_string()),
2680                request_line: RequestLine {
2681                    method: WithDefault::Some(HttpMethod::GET),
2682                    target: RequestTarget::from("https://httpbin.org/{{abc}}"),
2683                    http_version: WithDefault::default()
2684                },
2685                headers: vec![Header::new(
2686                    "Content-Type",
2687                    "multipart/form-data; boundary=\"--boundary--\""
2688                )],
2689                body: RequestBody::Multipart {
2690                    boundary: "--boundary--".to_string(),
2691                    parts: vec![Multipart {
2692                        disposition: DispositionField::new(""),
2693                        headers: vec![],
2694                        data: DataSource::Raw("".to_string())
2695                    }]
2696                },
2697                ..Default::default()
2698            }
2699        );
2700    }
2701
2702    #[test]
2703    pub fn parse_with_content_type_and_empty_body() {
2704        let str = r####"
2705POST https://test.com/formEncoded
2706Content-Type: application/json
2707"####;
2708
2709        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
2710        assert_eq!(errs, vec![]);
2711        assert_eq!(requests.len(), 1);
2712        let request = requests.remove(0);
2713
2714        assert_eq!(
2715            request.headers,
2716            vec![Header::new("Content-Type", "application/json")]
2717        );
2718
2719        assert_eq!(
2720            request.body,
2721            RequestBody::Raw {
2722                data: DataSource::Raw(String::new())
2723            }
2724        );
2725
2726        let str = r####"
2727POST https://test.com/formEncoded
2728"####;
2729
2730        let FileParseResult { mut requests, errs } = Parser::parse(str, false);
2731        assert_eq!(errs, vec![]);
2732        assert_eq!(requests.len(), 1);
2733        let request = requests.remove(0);
2734
2735        assert_eq!(request.headers, vec![]);
2736
2737        assert_eq!(request.body, RequestBody::None);
2738    }
2739}