// Copyright 2015 Matthew Holt and The Caddy Authors // Copyright 2023 Aaron Dewes and The Nirvati Project // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. use caddyfile::tokenizer::{Token, Tokenizer}; use pretty_assertions::assert_eq; #[test] fn very_simple() { let input = r#"host:123"#; let expect = vec![Token { file: None, line: 1, text: "host:123".to_string(), ..Default::default() }]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expect ); } #[test] fn one_directive() { let input = r#"host:123 directive"#; let expect = vec![ Token { file: None, line: 1, text: "host:123".to_string(), ..Default::default() }, Token { file: None, line: 3, text: "directive".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expect ); } #[test] fn directive_in_block() { let input = r#"host:123 { directive }"#; let expect = vec![ Token { file: None, line: 1, text: "host:123".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "{".to_string(), ..Default::default() }, Token { file: None, line: 2, text: "directive".to_string(), ..Default::default() }, Token { file: None, line: 3, text: "}".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expect ); } #[test] fn directive_in_one_line_block() { let input = r#"host:123 { directive }"#; let expect = vec![ Token { file: None, line: 1, text: "host:123".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "{".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "directive".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "}".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expect ); } #[test] fn comments() { let input = r#"host:123 { #comment directive # comment foobar # another comment }"#; let expect = vec![ Token { file: None, line: 1, text: "host:123".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "{".to_string(), ..Default::default() }, Token { file: None, line: 3, text: "directive".to_string(), ..Default::default() }, Token { file: None, line: 5, text: "foobar".to_string(), ..Default::default() }, Token { file: None, line: 6, text: "}".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expect ); } #[test] fn hash_within_string_is_not_a_comment() { let input = r#"host:123 { # hash inside string is not a comment redir / /some/#/path }"#; let expect = vec![ Token { file: None, line: 1, text: "host:123".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "{".to_string(), ..Default::default() }, Token { file: None, line: 3, text: "redir".to_string(), ..Default::default() }, Token { file: None, line: 3, text: "/".to_string(), ..Default::default() }, Token { file: None, line: 3, text: "/some/#/path".to_string(), ..Default::default() }, Token { file: None, line: 4, text: "}".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expect ); } #[test] fn more_comments() { let input = r#"# comment at beginning of file # comment at beginning of line host:123"#; let expect = vec![Token { file: None, line: 3, text: "host:123".to_string(), ..Default::default() }]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expect ); } #[test] fn quoted_value() { let input = r#"a "quoted value" b foobar"#; let expected = vec![ Token { file: None, line: 1, text: "a".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "quoted value".to_string(), was_quoted: Some('"'), ..Default::default() }, Token { file: None, line: 1, text: "b".to_string(), ..Default::default() }, Token { file: None, line: 2, text: "foobar".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn quoted_twice() { let input = r#"A "quoted \"value\" inside" B"#; let expected = vec![ Token { file: None, line: 1, text: "A".to_string(), ..Default::default() }, Token { file: None, line: 1, text: r#"quoted "value" inside"#.to_string(), was_quoted: Some('"'), ..Default::default() }, Token { file: None, line: 1, text: "B".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn escaped_newline_inside_quote() { let input = r#"An escaped "newline\ inside" quotes"#; let expected = vec![ Token { file: None, line: 1, text: "An".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "escaped".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "newline\\\ninside".to_string(), was_quoted: Some('"'), ..Default::default() }, Token { file: None, line: 2, text: "quotes".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn escaped_newline_outside_quote() { let input = r#"An escaped newline\ outside quotes"#; let expected = vec![ Token { file: None, line: 1, text: "An".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "escaped".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "newline".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "outside".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "quotes".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn many_escaped_newlines() { let input = "line1\\\nescaped\nline2\nline3"; let expected = vec![ Token { file: None, line: 1, text: "line1".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "escaped".to_string(), ..Default::default() }, Token { file: None, line: 3, text: "line2".to_string(), ..Default::default() }, Token { file: None, line: 4, text: "line3".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn even_more_escaped_newlines() { let input = "line1\\\nescaped1\\\nescaped2\nline4\nline5"; let expected = vec![ Token { file: None, line: 1, text: "line1".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "escaped1".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "escaped2".to_string(), ..Default::default() }, Token { file: None, line: 4, text: "line4".to_string(), ..Default::default() }, Token { file: None, line: 5, text: "line5".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn unescapable_in_quotes() { let input = r#""unescapable\ in quotes""#; let expected = vec![Token { file: None, line: 1, text: r#"unescapable\ in quotes"#.to_string(), was_quoted: Some('"'), ..Default::default() }]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn more_escape_tests() { let input_1 = r#""don't\escape""#; let expected_1 = vec![Token { file: None, line: 1, text: r#"don't\escape"#.to_string(), was_quoted: Some('"'), ..Default::default() }]; assert_eq!( Tokenizer::new(&mut input_1.as_bytes(), None).collect::>(), expected_1 ); let input_2 = r#"don't\\escape"#; let expected_2 = vec![Token { file: None, line: 1, text: r#"don't\\escape"#.to_string(), ..Default::default() }]; assert_eq!( Tokenizer::new(&mut input_2.as_bytes(), None).collect::>(), expected_2 ); let input_3 = r#"un\escapable"#; let expected_3 = vec![Token { file: None, line: 1, text: r#"un\escapable"#.to_string(), ..Default::default() }]; assert_eq!( Tokenizer::new(&mut input_3.as_bytes(), None).collect::>(), expected_3 ); } #[test] fn quoted_newline() { let input = r#"A "quoted value with line break inside" { foobar }"#; let expected = vec![ Token { file: None, line: 1, text: "A".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "quoted value with line\n\t\t\t\t\tbreak inside".to_string(), was_quoted: Some('"'), ..Default::default() }, Token { file: None, line: 2, text: "{".to_string(), ..Default::default() }, Token { file: None, line: 3, text: "foobar".to_string(), ..Default::default() }, Token { file: None, line: 4, text: "}".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn handle_win_paths() { let input = r#"C:\php\php-cgi.exe"#; let expected = vec![Token { file: None, line: 1, text: r#"C:\php\php-cgi.exe"#.to_string(), ..Default::default() }]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn empty_quotes() { let input = r#"empty "" string"#; let expected = vec![ Token { file: None, line: 1, text: "empty".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "".to_string(), was_quoted: Some('"'), ..Default::default() }, Token { file: None, line: 1, text: "string".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn skip_cr_characters() { let input = "skip those\r\nCR characters"; let expected = vec![ Token { file: None, line: 1, text: "skip".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "those".to_string(), ..Default::default() }, Token { file: None, line: 2, text: "CR".to_string(), ..Default::default() }, Token { file: None, line: 2, text: "characters".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn ignore_leading_byte_order_mark() { let input = "\u{feff}:8080"; let expected = vec![Token { file: None, line: 1, text: ":8080".to_string(), ..Default::default() }]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn handle_backtick_quoted() { let input = r#"simple `backtick quoted` string"#; let expected = vec![ Token { file: None, line: 1, text: "simple".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "backtick quoted".to_string(), was_quoted: Some('`'), ..Default::default() }, Token { file: None, line: 1, text: "string".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn multiline_backtick_quoted() { let input = r#"multiline `backtick quoted ` string"#; let expected = vec![ Token { file: None, line: 1, text: "multiline".to_string(), ..Default::default() }, Token { file: None, line: 1, text: "backtick\nquoted\n".to_string(), was_quoted: Some('`'), ..Default::default() }, Token { file: None, line: 3, text: "string".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn nested_quotes_inside_backticks() { let input = r#"nested `"quotes inside" backticks` string"#; let expected = vec![ Token { file: None, line: 1, text: "nested".to_string(), ..Default::default() }, Token { file: None, line: 1, text: r#""quotes inside" backticks"#.to_string(), was_quoted: Some('`'), ..Default::default() }, Token { file: None, line: 1, text: "string".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); } #[test] fn reverse_nested_quotes_inside_backticks() { let input = r#"reverse-nested "`backticks` inside" quotes"#; let expected = vec![ Token { file: None, line: 1, text: "reverse-nested".to_string(), ..Default::default() }, Token { file: None, line: 1, text: r#"`backticks` inside"#.to_string(), was_quoted: Some('"'), ..Default::default() }, Token { file: None, line: 1, text: "quotes".to_string(), ..Default::default() }, ]; assert_eq!( Tokenizer::new(&mut input.as_bytes(), None).collect::>(), expected ); }