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 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 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 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 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 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 => (), }
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 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 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 {
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 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 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 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 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 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 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 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 _ => 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 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 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 fn match_request_method(str: &str) -> model::HttpMethod {
545 model::HttpMethod::new(str)
547 }
548
549 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 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 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 [] => {
634 return Err(ParseErrorDetails {
635 error: ParseError::MissingRequestTargetLine,
636 details: None,
637 start_pos: Some(line_start.cursor),
638 end_pos: None,
639 });
640 } [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 fn parse_comment(scanner: &mut Scanner) -> Result<Option<model::Comment>, ParseErrorDetails> {
678 scanner.skip_empty_lines();
679 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 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 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 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 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 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" })
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 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 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 if peek_line.starts_with(REQUEST_SEPARATOR) {
876 break;
877 }
878
879 if peek_line.starts_with('>') {
881 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 if peek_line.starts_with(">>") {
895 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 RequestBody::Raw {
922 data: DataSource::Raw(body_str.trim_end_matches('\n').to_string()),
923 }
924 } else {
925 RequestBody::None
926 }
927 }
928
929 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 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 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(); 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 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 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 Ok(Multipart {
1109 disposition: field,
1110 headers: part_headers.to_vec(),
1111 data: DataSource::FromFilepath(file_path.to_string()), })
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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 pub fn is_multipart_boundary_valid() {
2513 let boundary = "";
2515 assert_eq!(Parser::is_multipart_boundary_valid(boundary).is_err(), true);
2516
2517 let boundary = "a".repeat(71);
2519 assert_eq!(
2520 Parser::is_multipart_boundary_valid(&boundary).is_err(),
2521 true
2522 );
2523
2524 let boundary = "a";
2526
2527 assert_eq!(
2528 Parser::is_multipart_boundary_valid(&boundary).is_err(),
2529 false
2530 );
2531
2532 let boundary = "a".repeat(70);
2534 assert_eq!(
2535 Parser::is_multipart_boundary_valid(&boundary).is_err(),
2536 false
2537 );
2538
2539 let boundary = "a b";
2541 assert_eq!(
2542 Parser::is_multipart_boundary_valid(&boundary).is_err(),
2543 true
2544 );
2545
2546 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 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 assert_eq!(errs.len(), 1);
2630 assert!(matches!(
2631 errs[0].details[0].error,
2632 ParseError::MissingMultipartHeaderBoundaryDefinition(_)
2633 ));
2634 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 assert_eq!(errs.len(), 1);
2674 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}