From 2d1cbcc6947dcc111ac105e24cf63e2d75ea688e Mon Sep 17 00:00:00 2001 From: jcamiel Date: Fri, 3 Mar 2023 13:55:43 +0100 Subject: [PATCH] Remove public visibility of mod http. --- packages/hurl/src/cli/fs.rs | 1 - packages/hurl/src/cli/options.rs | 39 ++-- packages/hurl/src/http/client.rs | 3 +- packages/hurl/src/http/context_dir.rs | 170 ------------------ packages/hurl/src/http/mod.rs | 8 +- packages/hurl/src/http/request.rs | 8 +- packages/hurl/src/http/request_spec.rs | 76 -------- .../hurl/src/http/request_spec_curl_args.rs | 28 ++- packages/hurl/src/http/response.rs | 110 +----------- packages/hurl/src/http/tests/libcurl.rs | 1 + packages/hurl/src/http/tests/mod.rs | 146 ++++++++++++++- packages/hurl/src/http/tests/runner.rs | 8 +- packages/hurl/src/lib.rs | 3 +- packages/hurl/src/main.rs | 7 +- packages/hurl/src/runner/assert.rs | 6 +- packages/hurl/src/runner/body.rs | 11 +- packages/hurl/src/runner/capture.rs | 6 +- packages/hurl/src/runner/hurl_file.rs | 18 +- packages/hurl/src/runner/multipart.rs | 18 +- packages/hurl/src/runner/query.rs | 24 +-- packages/hurl/src/runner/request.rs | 34 ++-- packages/hurl/src/runner/response.rs | 12 +- packages/hurl/src/runner/runner_options.rs | 2 +- packages/hurl/src/util/path.rs | 146 ++++++++++++++- 24 files changed, 392 insertions(+), 493 deletions(-) delete mode 100644 packages/hurl/src/http/context_dir.rs diff --git a/packages/hurl/src/cli/fs.rs b/packages/hurl/src/cli/fs.rs index ae8ec805c..10a3af2ad 100644 --- a/packages/hurl/src/cli/fs.rs +++ b/packages/hurl/src/cli/fs.rs @@ -15,7 +15,6 @@ * limitations under the License. * */ - use crate::cli::CliError; use std::fs; use std::fs::File; diff --git a/packages/hurl/src/cli/options.rs b/packages/hurl/src/cli/options.rs index abacdccef..8c1a9a3b5 100644 --- a/packages/hurl/src/cli/options.rs +++ b/packages/hurl/src/cli/options.rs @@ -15,7 +15,14 @@ * limitations under the License. * */ - +use crate::cli; +use crate::cli::CliError; +use crate::runner::RunnerOptionsBuilder; +use crate::runner::{RunnerOptions, Value, Verbosity}; +use atty::Stream; +use clap::{value_parser, ArgAction, ArgMatches, Command}; +use hurl::util::path::ContextDir; +use hurl_core::ast::Entry; use std::collections::HashMap; use std::env; use std::fs::File; @@ -23,16 +30,6 @@ use std::io::{BufRead, BufReader}; use std::path::{Path, PathBuf}; use std::time::Duration; -use atty::Stream; -use clap::{value_parser, ArgAction, ArgMatches, Command}; -use hurl::runner::RunnerOptionsBuilder; -use hurl_core::ast::Entry; - -use crate::cli; -use crate::cli::CliError; -use crate::http::{ClientOptions, ContextDir}; -use crate::runner::{RunnerOptions, Value, Verbosity}; - #[derive(Clone, Debug, PartialEq, Eq)] pub struct CliOptions { pub cacert_file: Option, @@ -82,18 +79,10 @@ pub enum OutputType { } pub fn app(version: &str) -> Command { - let ClientOptions { - connect_timeout: default_connect_timeout, - max_redirect: default_max_redirect, - retry_max_count: default_retry_max_count, - timeout: default_timeout, - .. - } = ClientOptions::default(); - - let default_connect_timeout = default_connect_timeout.as_secs(); - let default_max_redirect = default_max_redirect.unwrap(); - let default_timeout = default_timeout.as_secs(); - let default_retry_max_count = default_retry_max_count.unwrap(); + let default_connect_timeout = Duration::from_secs(300); + let default_max_redirect = 50; + let default_timeout = Duration::from_secs(300); + let default_retry_max_count = 10; Command::new("hurl") .about("Run Hurl file(s) or standard input") @@ -145,7 +134,7 @@ pub fn app(version: &str) -> Command { .long("connect-timeout") .value_name("SECONDS") .help("Maximum time allowed for connection") - .default_value(default_connect_timeout.to_string()) + .default_value(default_connect_timeout.as_secs().to_string()) .value_parser(value_parser!(u64)) .num_args(1) ) @@ -252,7 +241,7 @@ pub fn app(version: &str) -> Command { .short('m') .value_name("SECONDS") .help("Maximum time allowed for the transfer") - .default_value(default_timeout.to_string()) + .default_value(default_timeout.as_secs().to_string()) .allow_hyphen_values(true) .value_parser(value_parser!(u64)) .num_args(1) diff --git a/packages/hurl/src/http/client.rs b/packages/hurl/src/http/client.rs index f63f1c47a..2d95de233 100644 --- a/packages/hurl/src/http/client.rs +++ b/packages/hurl/src/http/client.rs @@ -30,8 +30,9 @@ use super::request_spec::*; use super::response::*; use super::{Header, HttpError, Verbosity}; use crate::http::certificate::Certificate; -use crate::http::{easy_ext, ContextDir}; +use crate::http::easy_ext; use crate::util::logger::Logger; +use crate::util::path::ContextDir; use base64::engine::general_purpose; use base64::Engine; use curl::easy::{List, SslOpt}; diff --git a/packages/hurl/src/http/context_dir.rs b/packages/hurl/src/http/context_dir.rs deleted file mode 100644 index dd1be1316..000000000 --- a/packages/hurl/src/http/context_dir.rs +++ /dev/null @@ -1,170 +0,0 @@ -/* - * Hurl (https://hurl.dev) - * Copyright (C) 2023 Orange - * - * 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 crate::util::path; -use std::path::{Path, PathBuf}; - -/// Represents the directories used to run a Hurl file. -#[derive(Clone, Debug, PartialEq, Eq)] -pub struct ContextDir { - /// The current working directory. - /// If current directory is a relative path, the `is_allowed` is not guaranteed to be correct. - current_dir: PathBuf, - /// The file root, either inferred or explicitly positioned by the user. - /// As a consequence, it is always defined (and can't be replaced by a `Option`). - /// It can be relative (to the current directory) or absolute. - file_root: PathBuf, -} - -impl Default for ContextDir { - fn default() -> Self { - ContextDir { - current_dir: PathBuf::new(), - file_root: PathBuf::new(), - } - } -} - -impl ContextDir { - /// Returns a context directory with the given current directory and file root. - pub fn new(current_dir: &Path, file_root: &Path) -> ContextDir { - ContextDir { - current_dir: PathBuf::from(current_dir), - file_root: PathBuf::from(file_root), - } - } - - /// Returns a path (absolute or relative), given a filename. - pub fn get_path(&self, filename: &str) -> PathBuf { - self.file_root.join(Path::new(filename)) - } - - /// Checks if a given filename access is authorized. - /// This method is used to check if a local file can be included in POST request. - pub fn is_access_allowed(&self, filename: &str) -> bool { - let file = self.get_path(filename); - let absolute_file = self.current_dir.join(file); - let absolute_file_root = self.current_dir.join(&self.file_root); - path::is_descendant(absolute_file.as_path(), absolute_file_root.as_path()) - } -} - -#[cfg(test)] -mod tests { - use crate::http::ContextDir; - use std::path::Path; - - #[test] - fn check_filename_allowed_access_without_user_file_root() { - // ``` - // $ cd /tmp - // $ hurl test.hurl - // ``` - let current_dir = Path::new("/tmp"); - let file_root = Path::new(""); - let context_dir = ContextDir::new(current_dir, file_root); - assert!(context_dir.is_access_allowed("foo.bin")); - assert!(context_dir.is_access_allowed("/tmp/foo.bin")); - assert!(context_dir.is_access_allowed("a/foo.bin")); - assert!(context_dir.is_access_allowed("a/b/foo.bin")); - assert!(context_dir.is_access_allowed("../tmp/a/b/foo.bin")); - assert!(context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); - - assert!(!context_dir.is_access_allowed("/file/foo.bin")); - assert!(!context_dir.is_access_allowed("../foo.bin")); - assert!(!context_dir.is_access_allowed("../../foo.bin")); - assert!(!context_dir.is_access_allowed("../../file/foo.bin")); - } - - #[test] - fn check_filename_allowed_access_with_explicit_absolute_user_file_root() { - // ``` - // $ cd /tmp - // $ hurl --file-root /file test.hurl - // ``` - let current_dir = Path::new("/tmp"); - let file_root = Path::new("/file"); - let context_dir = ContextDir::new(current_dir, file_root); - assert!(context_dir.is_access_allowed("foo.bin")); // absolute path is /file/foo.bin - assert!(context_dir.is_access_allowed("/file/foo.bin")); - assert!(context_dir.is_access_allowed("a/foo.bin")); - assert!(context_dir.is_access_allowed("a/b/foo.bin")); - assert!(context_dir.is_access_allowed("../../file/foo.bin")); - - assert!(!context_dir.is_access_allowed("/tmp/foo.bin")); - assert!(!context_dir.is_access_allowed("../tmp/a/b/foo.bin")); - assert!(!context_dir.is_access_allowed("../foo.bin")); - assert!(!context_dir.is_access_allowed("../../foo.bin")); - assert!(!context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); - - let current_dir = Path::new("/tmp"); - let file_root = Path::new("../file"); - let context_dir = ContextDir::new(current_dir, file_root); - assert!(context_dir.is_access_allowed("foo.bin")); - assert!(context_dir.is_access_allowed("/file/foo.bin")); - assert!(context_dir.is_access_allowed("a/foo.bin")); - assert!(context_dir.is_access_allowed("a/b/foo.bin")); - assert!(context_dir.is_access_allowed("../../file/foo.bin")); - - assert!(!context_dir.is_access_allowed("/tmp/foo.bin")); - assert!(!context_dir.is_access_allowed("../tmp/a/b/foo.bin")); - assert!(!context_dir.is_access_allowed("../foo.bin")); - assert!(!context_dir.is_access_allowed("../../foo.bin")); - assert!(!context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); - } - - #[test] - fn check_filename_allowed_access_with_implicit_relative_user_file_root() { - // ``` - // $ cd /tmp - // $ hurl a/b/test.hurl - // ``` - let current_dir = Path::new("/tmp"); - let file_root = Path::new("a/b"); - let context_dir = ContextDir::new(current_dir, file_root); - assert!(context_dir.is_access_allowed("foo.bin")); - assert!(context_dir.is_access_allowed("c/foo.bin")); // absolute path is /tmp/a/b/c/foo.bin - assert!(context_dir.is_access_allowed("/tmp/a/b/foo.bin")); - assert!(context_dir.is_access_allowed("/tmp/a/b/c/d/foo.bin")); - assert!(context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); - - assert!(!context_dir.is_access_allowed("/tmp/foo.bin")); - } - - #[test] - fn check_filename_allowed_access_with_explicit_relative_user_file_root() { - // ``` - // $ cd /tmp - // $ hurl --file-root ../tmp test.hurl - // ``` - let current_dir = Path::new("/tmp"); - let file_root = Path::new("../tmp"); - let context_dir = ContextDir::new(current_dir, file_root); - assert!(context_dir.is_access_allowed("foo.bin")); - assert!(context_dir.is_access_allowed("/tmp/foo.bin")); - assert!(context_dir.is_access_allowed("a/foo.bin")); - assert!(context_dir.is_access_allowed("a/b/foo.bin")); - assert!(context_dir.is_access_allowed("../tmp/a/b/foo.bin")); - assert!(context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); - - assert!(!context_dir.is_access_allowed("/file/foo.bin")); - assert!(!context_dir.is_access_allowed("../foo.bin")); - assert!(!context_dir.is_access_allowed("../../foo.bin")); - assert!(!context_dir.is_access_allowed("../../file/foo.bin")); - } -} diff --git a/packages/hurl/src/http/mod.rs b/packages/hurl/src/http/mod.rs index da4b8b457..76f31c750 100644 --- a/packages/hurl/src/http/mod.rs +++ b/packages/hurl/src/http/mod.rs @@ -18,24 +18,20 @@ pub use self::certificate::Certificate; pub use self::client::Client; -pub use self::context_dir::ContextDir; pub use self::cookie::{CookieAttribute, ResponseCookie}; pub use self::core::{Cookie, Param, RequestCookie}; pub use self::error::HttpError; pub use self::header::Header; pub use self::options::{ClientOptions, Verbosity}; pub use self::request::Request; -#[cfg(test)] -pub use self::request_spec::tests::*; pub use self::request_spec::{Body, FileParam, Method, MultipartParam, RequestSpec}; -#[cfg(test)] -pub use self::response::tests::*; pub use self::response::{Response, Version}; +#[cfg(test)] +pub use self::tests::*; pub use self::version::libcurl_version_info; mod certificate; mod client; -mod context_dir; mod cookie; mod core; mod debug; diff --git a/packages/hurl/src/http/request.rs b/packages/hurl/src/http/request.rs index 81b9dbfc8..7d17e1929 100644 --- a/packages/hurl/src/http/request.rs +++ b/packages/hurl/src/http/request.rs @@ -101,11 +101,11 @@ fn parse_cookie(s: &str) -> RequestCookie { } #[cfg(test)] -pub mod tests { +mod tests { use super::*; use crate::http::RequestCookie; - pub fn hello_request() -> Request { + fn hello_request() -> Request { Request { method: "GET".to_string(), url: "http://localhost:8000/hello".to_string(), @@ -118,7 +118,7 @@ pub mod tests { } } - pub fn query_string_request() -> Request { + fn query_string_request() -> Request { Request { method: "GET".to_string(), url: "http://localhost:8000/querystring-params?param1=value1¶m2=¶m3=a%3Db¶m4=1%2C2%2C3".to_string(), @@ -127,7 +127,7 @@ pub mod tests { } } - pub fn cookies_request() -> Request { + fn cookies_request() -> Request { Request { method: "GET".to_string(), url: "http://localhost:8000/cookies".to_string(), diff --git a/packages/hurl/src/http/request_spec.rs b/packages/hurl/src/http/request_spec.rs index 7b0795a5a..e7331a8dd 100644 --- a/packages/hurl/src/http/request_spec.rs +++ b/packages/hurl/src/http/request_spec.rs @@ -150,79 +150,3 @@ impl fmt::Display for FileParam { ) } } - -#[cfg(test)] -pub mod tests { - use super::*; - - pub fn hello_http_request() -> RequestSpec { - RequestSpec { - method: Method::Get, - url: "http://localhost:8000/hello".to_string(), - ..Default::default() - } - } - - pub fn custom_http_request() -> RequestSpec { - RequestSpec { - method: Method::Get, - url: "http://localhost/custom".to_string(), - headers: vec![ - Header::new("User-Agent", "iPhone"), - Header::new("Foo", "Bar"), - ], - cookies: vec![ - RequestCookie { - name: String::from("theme"), - value: String::from("light"), - }, - RequestCookie { - name: String::from("sessionToken"), - value: String::from("abc123"), - }, - ], - ..Default::default() - } - } - - pub fn query_http_request() -> RequestSpec { - RequestSpec { - method: Method::Get, - url: "http://localhost:8000/querystring-params".to_string(), - querystring: vec![ - Param { - name: String::from("param1"), - value: String::from("value1"), - }, - Param { - name: String::from("param2"), - value: String::from("a b"), - }, - ], - ..Default::default() - } - } - - pub fn form_http_request() -> RequestSpec { - RequestSpec { - method: Method::Post, - url: "http://localhost/form-params".to_string(), - headers: vec![Header::new( - "Content-Type", - "application/x-www-form-urlencoded", - )], - form: vec![ - Param { - name: String::from("param1"), - value: String::from("value1"), - }, - Param { - name: String::from("param2"), - value: String::from("a b"), - }, - ], - content_type: Some("multipart/form-data".to_string()), - ..Default::default() - } - } -} diff --git a/packages/hurl/src/http/request_spec_curl_args.rs b/packages/hurl/src/http/request_spec_curl_args.rs index ba4a8a37a..b5dae0375 100644 --- a/packages/hurl/src/http/request_spec_curl_args.rs +++ b/packages/hurl/src/http/request_spec_curl_args.rs @@ -15,10 +15,10 @@ * limitations under the License. * */ - use super::core::*; use super::RequestSpec; use crate::http::*; +use crate::util::path::ContextDir; use std::collections::HashMap; impl RequestSpec { @@ -235,8 +235,32 @@ fn escape_string(s: &str) -> String { #[cfg(test)] pub mod tests { use super::*; + use crate::http; use std::path::Path; + fn form_http_request() -> RequestSpec { + RequestSpec { + method: Method::Post, + url: "http://localhost/form-params".to_string(), + headers: vec![Header::new( + "Content-Type", + "application/x-www-form-urlencoded", + )], + form: vec![ + Param { + name: String::from("param1"), + value: String::from("value1"), + }, + Param { + name: String::from("param2"), + value: String::from("a b"), + }, + ], + content_type: Some("multipart/form-data".to_string()), + ..Default::default() + } + } + #[test] fn test_encode_byte() { assert_eq!(encode_byte(1), "\\x01".to_string()); @@ -322,7 +346,7 @@ pub mod tests { fn requests_curl_args() { let context_dir = &ContextDir::default(); assert_eq!( - hello_http_request().curl_args(context_dir), + http::hello_http_request().curl_args(context_dir), vec!["'http://localhost:8000/hello'".to_string()] ); assert_eq!( diff --git a/packages/hurl/src/http/response.rs b/packages/hurl/src/http/response.rs index 7b4f318f0..6fe68f465 100644 --- a/packages/hurl/src/http/response.rs +++ b/packages/hurl/src/http/response.rs @@ -81,117 +81,9 @@ impl Response { } #[cfg(test)] -pub mod tests { +mod tests { use super::*; - pub fn hello_http_response() -> Response { - Response { - headers: vec![ - Header::new("Content-Type", "text/html; charset=utf-8"), - Header::new("Content-Length", "12"), - ], - body: String::into_bytes(String::from("Hello World!")), - ..Default::default() - } - } - - pub fn html_http_response() -> Response { - Response { - headers: vec![Header::new("Content-Type", "text/html; charset=utf-8")], - body: String::into_bytes(String::from( - "
", - )), - ..Default::default() - } - } - - pub fn xml_invalid_response() -> Response { - Response { - headers: vec![ - Header::new("Content-Type", "text/html; charset=utf-8"), - Header::new("Content-Length", "12"), - ], - body: String::into_bytes( - r#" -xxx -"# - .to_string(), - ), - ..Default::default() - } - } - - pub fn xml_two_users_http_response() -> Response { - Response { - headers: vec![ - Header::new("Content-Type", "text/html; charset=utf-8"), - Header::new("Content-Length", "12"), - ], - body: String::into_bytes( - r#" - - - Bob - Bill - -"# - .to_string(), - ), - ..Default::default() - } - } - - pub fn xml_three_users_http_response() -> Response { - Response { - headers: vec![ - Header::new("Content-Type", "text/html; charset=utf-8"), - Header::new("Content-Length", "12"), - ], - body: String::into_bytes( - r#" - - - Bob - Bill - Bruce - -"# - .to_string(), - ), - ..Default::default() - } - } - - pub fn json_http_response() -> Response { - Response { - body: String::into_bytes( - r#" -{ - "success":false, - "errors": [ - { "id": "error1"}, - {"id": "error2"} - ], - "duration": 1.5 -} -"# - .to_string(), - ), - ..Default::default() - } - } - - pub fn bytes_http_response() -> Response { - Response { - headers: vec![ - Header::new("Content-Type", "application/octet-stream"), - Header::new("Content-Length", "1"), - ], - body: vec![255], - ..Default::default() - } - } - #[test] fn get_header_values() { let response = Response { diff --git a/packages/hurl/src/http/tests/libcurl.rs b/packages/hurl/src/http/tests/libcurl.rs index 791dec349..f47ec38d1 100644 --- a/packages/hurl/src/http/tests/libcurl.rs +++ b/packages/hurl/src/http/tests/libcurl.rs @@ -22,6 +22,7 @@ use std::time::Duration; use crate::http::*; use crate::util::logger::LoggerBuilder; +use crate::util::path::ContextDir; fn default_get_request(url: &str) -> RequestSpec { RequestSpec { diff --git a/packages/hurl/src/http/tests/mod.rs b/packages/hurl/src/http/tests/mod.rs index 7daf982a7..f5bef0c68 100644 --- a/packages/hurl/src/http/tests/mod.rs +++ b/packages/hurl/src/http/tests/mod.rs @@ -15,5 +15,149 @@ * limitations under the License. * */ +use crate::http::{Header, Method, Param, RequestCookie, RequestSpec, Response}; + mod libcurl; -mod runner; \ No newline at end of file +mod runner; + +/// Some Request Response to be used by tests + +pub fn hello_http_request() -> RequestSpec { + RequestSpec { + method: Method::Get, + url: "http://localhost:8000/hello".to_string(), + ..Default::default() + } +} + +pub fn json_http_response() -> Response { + Response { + body: String::into_bytes( + r#" +{ + "success":false, + "errors": [ + { "id": "error1"}, + {"id": "error2"} + ], + "duration": 1.5 +} +"# + .to_string(), + ), + ..Default::default() + } +} + +pub fn xml_two_users_http_response() -> Response { + Response { + headers: vec![ + Header::new("Content-Type", "text/html; charset=utf-8"), + Header::new("Content-Length", "12"), + ], + body: String::into_bytes( + r#" + + + Bob + Bill + +"# + .to_string(), + ), + ..Default::default() + } +} + +pub fn xml_three_users_http_response() -> Response { + Response { + headers: vec![ + Header::new("Content-Type", "text/html; charset=utf-8"), + Header::new("Content-Length", "12"), + ], + body: String::into_bytes( + r#" + + + Bob + Bill + Bruce + +"# + .to_string(), + ), + ..Default::default() + } +} + +pub fn hello_http_response() -> Response { + Response { + headers: vec![ + Header::new("Content-Type", "text/html; charset=utf-8"), + Header::new("Content-Length", "12"), + ], + body: String::into_bytes(String::from("Hello World!")), + ..Default::default() + } +} + +pub fn bytes_http_response() -> Response { + Response { + headers: vec![ + Header::new("Content-Type", "application/octet-stream"), + Header::new("Content-Length", "1"), + ], + body: vec![255], + ..Default::default() + } +} + +pub fn html_http_response() -> Response { + Response { + headers: vec![Header::new("Content-Type", "text/html; charset=utf-8")], + body: String::into_bytes(String::from( + "
", + )), + ..Default::default() + } +} + +pub fn query_http_request() -> RequestSpec { + RequestSpec { + method: Method::Get, + url: "http://localhost:8000/querystring-params".to_string(), + querystring: vec![ + Param { + name: String::from("param1"), + value: String::from("value1"), + }, + Param { + name: String::from("param2"), + value: String::from("a b"), + }, + ], + ..Default::default() + } +} + +pub fn custom_http_request() -> RequestSpec { + RequestSpec { + method: Method::Get, + url: "http://localhost/custom".to_string(), + headers: vec![ + Header::new("User-Agent", "iPhone"), + Header::new("Foo", "Bar"), + ], + cookies: vec![ + RequestCookie { + name: String::from("theme"), + value: String::from("light"), + }, + RequestCookie { + name: String::from("sessionToken"), + value: String::from("abc123"), + }, + ], + ..Default::default() + } +} diff --git a/packages/hurl/src/http/tests/runner.rs b/packages/hurl/src/http/tests/runner.rs index c01b02282..5411a0a4c 100644 --- a/packages/hurl/src/http/tests/runner.rs +++ b/packages/hurl/src/http/tests/runner.rs @@ -18,12 +18,11 @@ use std::collections::HashMap; -use hurl_core::ast::*; -use crate::{http, runner}; +use crate::runner; use crate::runner::RunnerOptions; use crate::util::logger::LoggerBuilder; +use hurl_core::ast::*; -#[cfg(test)] fn hello_request() -> Request { // GET http://localhost;8000/hello let source_info = SourceInfo { @@ -86,8 +85,6 @@ fn hello_request() -> Request { #[test] fn test_hello() { - let mut client = http::Client::new(None); - // We construct a Hurl file ast "by hand", with fake source info. // In this particular case, the raw content is empty as the Hurl file hasn't // been built from a text content. @@ -138,7 +135,6 @@ fn test_hello() { &hurl_file, content, filename, - &mut client, &runner_options, &variables, &logger, diff --git a/packages/hurl/src/lib.rs b/packages/hurl/src/lib.rs index 1b50bffd7..3f82351db 100644 --- a/packages/hurl/src/lib.rs +++ b/packages/hurl/src/lib.rs @@ -18,10 +18,11 @@ #![cfg_attr(feature = "strict", deny(warnings))] mod html; -pub mod http; +mod http; mod json; mod jsonpath; pub mod output; pub mod report; pub mod runner; pub mod util; +pub use http::libcurl_version_info; diff --git a/packages/hurl/src/main.rs b/packages/hurl/src/main.rs index dd84453c5..f91062a73 100644 --- a/packages/hurl/src/main.rs +++ b/packages/hurl/src/main.rs @@ -31,7 +31,7 @@ use hurl::report::html; use hurl::runner; use hurl::runner::HurlResult; use hurl::util::logger::{BaseLogger, Logger, LoggerBuilder}; -use hurl::{http, output}; +use hurl::{libcurl_version_info, output}; use hurl_core::ast::HurlFile; use hurl_core::parser; use report::junit; @@ -58,7 +58,7 @@ pub struct HurlRun { fn main() { init_colored(); - let libcurl_version = http::libcurl_version_info(); + let libcurl_version = libcurl_version_info(); let version_info = format!( "{} {}\nFeatures (libcurl): {}\nFeatures (built-in): brotli", clap::crate_version!(), @@ -216,15 +216,12 @@ fn execute( log_run_info(hurl_file, cli_options, logger); let variables = &cli_options.variables; - let cookie_input_file = cli_options.cookie_input_file.clone(); let runner_options = cli_options.to(filename, current_dir); - let mut client = http::Client::new(cookie_input_file); runner::run( hurl_file, content, filename, - &mut client, &runner_options, variables, logger, diff --git a/packages/hurl/src/runner/assert.rs b/packages/hurl/src/runner/assert.rs index 89e440252..f800c9031 100644 --- a/packages/hurl/src/runner/assert.rs +++ b/packages/hurl/src/runner/assert.rs @@ -179,10 +179,10 @@ pub fn eval_assert( #[cfg(test)] pub mod tests { - use hurl_core::ast::SourceInfo; - use super::super::query; use super::*; + use crate::http::xml_three_users_http_response; + use hurl_core::ast::SourceInfo; // xpath //user countEquals 3 pub fn assert_count_user() -> Assert { @@ -226,7 +226,7 @@ pub mod tests { eval_assert( &assert_count_user(), &variables, - &http::xml_three_users_http_response() + &xml_three_users_http_response(), ), AssertResult::Explicit { actual: Ok(Some(Value::Nodeset(3))), diff --git a/packages/hurl/src/runner/body.rs b/packages/hurl/src/runner/body.rs index b8cfea116..1a426fa55 100644 --- a/packages/hurl/src/runner/body.rs +++ b/packages/hurl/src/runner/body.rs @@ -15,19 +15,16 @@ * limitations under the License. * */ - -use std::collections::HashMap; -use std::path::PathBuf; - -use hurl_core::ast::*; - use super::core::{Error, RunnerError}; use super::json::eval_json_value; use super::value::Value; use crate::http; -use crate::http::ContextDir; use crate::runner::multiline::eval_multiline; use crate::runner::template::eval_template; +use crate::util::path::ContextDir; +use hurl_core::ast::*; +use std::collections::HashMap; +use std::path::PathBuf; pub fn eval_body( body: &Body, diff --git a/packages/hurl/src/runner/capture.rs b/packages/hurl/src/runner/capture.rs index cd0c4c2a7..defac276b 100644 --- a/packages/hurl/src/runner/capture.rs +++ b/packages/hurl/src/runner/capture.rs @@ -57,11 +57,9 @@ pub fn eval_capture( #[cfg(test)] pub mod tests { - use hurl_core::ast::{Pos, SourceInfo}; - - use super::*; - use self::super::super::query; + use super::*; + use hurl_core::ast::{Pos, SourceInfo}; pub fn user_count_capture() -> Capture { // non scalar value diff --git a/packages/hurl/src/runner/hurl_file.rs b/packages/hurl/src/runner/hurl_file.rs index e842d9c23..f08112329 100644 --- a/packages/hurl/src/runner/hurl_file.rs +++ b/packages/hurl/src/runner/hurl_file.rs @@ -29,8 +29,8 @@ use crate::util::logger::LoggerBuilder; use hurl_core::ast::VersionValue::VersionAnyLegacy; use hurl_core::ast::*; -/// Runs a `hurl_file`, issue from the given `content`and `filename`, with -/// an `http_client`. Returns a [`HurlResult`] upon completion. +/// Runs a `hurl_file`, issue from the given `content`and `filename` and +/// returns a [`HurlResult`] upon completion. /// /// `filename` and `content` are used to display rich logs (for parsing error or asserts /// failures). @@ -41,13 +41,11 @@ use hurl_core::ast::*; /// use std::collections::HashMap; /// use std::path::PathBuf; /// use hurl_core::parser; -/// use hurl::http; -/// use hurl::http::ContextDir; /// use hurl::runner; /// use hurl::runner::{Value, RunnerOptionsBuilder, Verbosity}; /// use hurl::util::logger::LoggerBuilder; /// -/// // Parse Hurl file +/// // Parse the Hurl file /// let filename = "sample.hurl"; /// let content = r#" /// GET http://localhost:8000/hello @@ -55,8 +53,6 @@ use hurl_core::ast::*; /// "#; /// let hurl_file = parser::parse_hurl_file(content).unwrap(); /// -/// // Create an HTTP client -/// let mut client = http::Client::new(None); /// let logger = LoggerBuilder::new().build(); /// /// // Define runner options @@ -69,12 +65,11 @@ use hurl_core::ast::*; /// let mut variables = HashMap::default(); /// variables.insert("name".to_string(), Value::String("toto".to_string())); /// -/// // Run the hurl file +/// // Run the Hurl file /// let hurl_results = runner::run( /// &hurl_file, /// content, /// filename, -/// &mut client, /// &runner_options, /// &variables, /// &logger @@ -85,11 +80,12 @@ pub fn run( hurl_file: &HurlFile, content: &str, filename: &str, - http_client: &mut http::Client, runner_options: &RunnerOptions, variables: &HashMap, logger: &Logger, ) -> HurlResult { + let cookie_input_file = runner_options.cookie_input_file.clone(); + let mut http_client = http::Client::new(cookie_input_file); let mut entries = vec![]; let mut variables = variables.clone(); let mut entry_index = 1; @@ -140,7 +136,7 @@ pub fn run( Ok(options) => entry::run( entry, entry_index, - http_client, + &mut http_client, &mut variables, options, &logger, diff --git a/packages/hurl/src/runner/multipart.rs b/packages/hurl/src/runner/multipart.rs index 5482f8e27..8402f1144 100644 --- a/packages/hurl/src/runner/multipart.rs +++ b/packages/hurl/src/runner/multipart.rs @@ -15,20 +15,16 @@ * limitations under the License. * */ -use std::collections::HashMap; -use std::ffi::OsStr; -#[allow(unused)] -use std::io::prelude::*; -use std::path::Path; - -use crate::http; -use crate::http::ContextDir; -use crate::runner::body::eval_file; -use hurl_core::ast::*; - use super::core::Error; use super::template::eval_template; use super::value::Value; +use crate::http; +use crate::runner::body::eval_file; +use crate::util::path::ContextDir; +use hurl_core::ast::*; +use std::collections::HashMap; +use std::ffi::OsStr; +use std::path::Path; pub fn eval_multipart_param( multipart_param: &MultipartParam, diff --git a/packages/hurl/src/runner/query.rs b/packages/hurl/src/runner/query.rs index 1baa19e8e..fe5813532 100644 --- a/packages/hurl/src/runner/query.rs +++ b/packages/hurl/src/runner/query.rs @@ -449,24 +449,6 @@ pub mod tests { } } - pub fn json_http_response() -> http::Response { - http::Response { - body: String::into_bytes( - r#" -{ - "success":false, - "errors": [ - { "id": "error1"}, - {"id": "error2"} - ] -} -"# - .to_string(), - ), - ..Default::default() - } - } - pub fn jsonpath_success() -> Query { // jsonpath $.success Query { @@ -1016,7 +998,7 @@ pub mod tests { }, }; - let error = eval_query(&jsonpath_query, &variables, &json_http_response()) + let error = eval_query(&jsonpath_query, &variables, &http::json_http_response()) .err() .unwrap(); assert_eq!( @@ -1066,13 +1048,13 @@ pub mod tests { fn test_query_json() { let variables = HashMap::new(); assert_eq!( - eval_query(&jsonpath_success(), &variables, &json_http_response()) + eval_query(&jsonpath_success(), &variables, &http::json_http_response()) .unwrap() .unwrap(), Value::Bool(false) ); assert_eq!( - eval_query(&jsonpath_errors(), &variables, &json_http_response()) + eval_query(&jsonpath_errors(), &variables, &http::json_http_response()) .unwrap() .unwrap(), Value::List(vec![ diff --git a/packages/hurl/src/runner/request.rs b/packages/hurl/src/runner/request.rs index bedf063c9..9f3a6a060 100644 --- a/packages/hurl/src/runner/request.rs +++ b/packages/hurl/src/runner/request.rs @@ -15,22 +15,17 @@ * limitations under the License. * */ - -use base64::engine::general_purpose; -use base64::Engine; -use std::collections::HashMap; -#[allow(unused)] -use std::io::prelude::*; - -use crate::http; -use crate::http::ContextDir; -use hurl_core::ast::*; - use super::body::eval_body; use super::core::Error; use super::template::eval_template; use super::value::Value; +use crate::http; use crate::runner::multipart::eval_multipart_param; +use crate::util::path::ContextDir; +use base64::engine::general_purpose; +use base64::Engine; +use hurl_core::ast::*; +use std::collections::HashMap; /// Transforms an AST `request` to a spec request given a set of `variables`. pub fn eval_request( @@ -186,19 +181,18 @@ fn eval_method(method: &Method) -> http::Method { #[cfg(test)] mod tests { - use hurl_core::ast::SourceInfo; - use super::super::core::RunnerError; use super::*; + use hurl_core::ast::SourceInfo; - pub fn whitespace() -> Whitespace { + fn whitespace() -> Whitespace { Whitespace { value: String::from(" "), source_info: SourceInfo::new(0, 0, 0, 0), } } - pub fn hello_request() -> Request { + fn hello_request() -> Request { let line_terminator = LineTerminator { space0: whitespace(), comment: None, @@ -235,7 +229,7 @@ mod tests { } } - pub fn simple_key_value(key: EncodedString, value: Template) -> KeyValue { + fn simple_key_value(key: EncodedString, value: Template) -> KeyValue { let line_terminator = LineTerminator { space0: whitespace(), comment: None, @@ -252,7 +246,7 @@ mod tests { } } - pub fn query_request() -> Request { + fn query_request() -> Request { let line_terminator = LineTerminator { space0: whitespace(), comment: None, @@ -323,7 +317,7 @@ mod tests { } #[test] - pub fn test_error_variable() { + fn test_error_variable() { let variables = HashMap::new(); let error = eval_request(&hello_request(), &variables, &ContextDir::default()) .err() @@ -338,7 +332,7 @@ mod tests { } #[test] - pub fn test_hello_request() { + fn test_hello_request() { let mut variables = HashMap::new(); variables.insert( String::from("base_url"), @@ -350,7 +344,7 @@ mod tests { } #[test] - pub fn test_query_request() { + fn test_query_request() { let mut variables = HashMap::new(); variables.insert( String::from("param1"), diff --git a/packages/hurl/src/runner/response.rs b/packages/hurl/src/runner/response.rs index d0b6ddff2..8c2734c9d 100644 --- a/packages/hurl/src/runner/response.rs +++ b/packages/hurl/src/runner/response.rs @@ -15,13 +15,6 @@ * limitations under the License. * */ -use std::collections::HashMap; - -use crate::http; -use crate::http::ContextDir; -use crate::runner::multiline::eval_multiline; -use hurl_core::ast::*; - use super::assert::eval_assert; use super::body::eval_body; use super::capture::eval_capture; @@ -29,6 +22,11 @@ use super::core::*; use super::json::eval_json_value; use super::template::eval_template; use super::value::Value; +use crate::http; +use crate::runner::multiline::eval_multiline; +use crate::util::path::ContextDir; +use hurl_core::ast::*; +use std::collections::HashMap; /// Returns a list of assert results on the response status code and HTTP version, /// given a set of `variables`, an actual `http_response` and a spec `response`. diff --git a/packages/hurl/src/runner/runner_options.rs b/packages/hurl/src/runner/runner_options.rs index 065a879ea..466af9dad 100644 --- a/packages/hurl/src/runner/runner_options.rs +++ b/packages/hurl/src/runner/runner_options.rs @@ -15,8 +15,8 @@ * limitations under the License. * */ -use crate::http::ContextDir; use crate::runner::Verbosity; +use crate::util::path::ContextDir; use hurl_core::ast::Entry; use std::time::Duration; diff --git a/packages/hurl/src/util/path.rs b/packages/hurl/src/util/path.rs index b3ba17bb1..6760998a0 100644 --- a/packages/hurl/src/util/path.rs +++ b/packages/hurl/src/util/path.rs @@ -17,8 +17,53 @@ */ use std::path::{Component, Path, PathBuf}; +/// Represents the directories used to run a Hurl file. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct ContextDir { + /// The current working directory. + /// If current directory is a relative path, the `is_allowed` is not guaranteed to be correct. + current_dir: PathBuf, + /// The file root, either inferred or explicitly positioned by the user. + /// As a consequence, it is always defined (and can't be replaced by a `Option`). + /// It can be relative (to the current directory) or absolute. + file_root: PathBuf, +} + +impl Default for ContextDir { + fn default() -> Self { + ContextDir { + current_dir: PathBuf::new(), + file_root: PathBuf::new(), + } + } +} + +impl ContextDir { + /// Returns a context directory with the given current directory and file root. + pub fn new(current_dir: &Path, file_root: &Path) -> ContextDir { + ContextDir { + current_dir: PathBuf::from(current_dir), + file_root: PathBuf::from(file_root), + } + } + + /// Returns a path (absolute or relative), given a filename. + pub fn get_path(&self, filename: &str) -> PathBuf { + self.file_root.join(Path::new(filename)) + } + + /// Checks if a given filename access is authorized. + /// This method is used to check if a local file can be included in POST request. + pub fn is_access_allowed(&self, filename: &str) -> bool { + let file = self.get_path(filename); + let absolute_file = self.current_dir.join(file); + let absolute_file_root = self.current_dir.join(&self.file_root); + is_descendant(absolute_file.as_path(), absolute_file_root.as_path()) + } +} + /// Return true if `path` is a descendant path of `ancestor`, false otherwise. -pub fn is_descendant(path: &Path, ancestor: &Path) -> bool { +fn is_descendant(path: &Path, ancestor: &Path) -> bool { let path = normalize_path(path); let ancestor = normalize_path(ancestor); for a in path.ancestors() { @@ -65,6 +110,105 @@ fn normalize_path(path: &Path) -> PathBuf { mod tests { use super::*; + #[test] + fn check_filename_allowed_access_without_user_file_root() { + // ``` + // $ cd /tmp + // $ hurl test.hurl + // ``` + let current_dir = Path::new("/tmp"); + let file_root = Path::new(""); + let context_dir = ContextDir::new(current_dir, file_root); + assert!(context_dir.is_access_allowed("foo.bin")); + assert!(context_dir.is_access_allowed("/tmp/foo.bin")); + assert!(context_dir.is_access_allowed("a/foo.bin")); + assert!(context_dir.is_access_allowed("a/b/foo.bin")); + assert!(context_dir.is_access_allowed("../tmp/a/b/foo.bin")); + assert!(context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); + + assert!(!context_dir.is_access_allowed("/file/foo.bin")); + assert!(!context_dir.is_access_allowed("../foo.bin")); + assert!(!context_dir.is_access_allowed("../../foo.bin")); + assert!(!context_dir.is_access_allowed("../../file/foo.bin")); + } + + #[test] + fn check_filename_allowed_access_with_explicit_absolute_user_file_root() { + // ``` + // $ cd /tmp + // $ hurl --file-root /file test.hurl + // ``` + let current_dir = Path::new("/tmp"); + let file_root = Path::new("/file"); + let context_dir = ContextDir::new(current_dir, file_root); + assert!(context_dir.is_access_allowed("foo.bin")); // absolute path is /file/foo.bin + assert!(context_dir.is_access_allowed("/file/foo.bin")); + assert!(context_dir.is_access_allowed("a/foo.bin")); + assert!(context_dir.is_access_allowed("a/b/foo.bin")); + assert!(context_dir.is_access_allowed("../../file/foo.bin")); + + assert!(!context_dir.is_access_allowed("/tmp/foo.bin")); + assert!(!context_dir.is_access_allowed("../tmp/a/b/foo.bin")); + assert!(!context_dir.is_access_allowed("../foo.bin")); + assert!(!context_dir.is_access_allowed("../../foo.bin")); + assert!(!context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); + + let current_dir = Path::new("/tmp"); + let file_root = Path::new("../file"); + let context_dir = ContextDir::new(current_dir, file_root); + assert!(context_dir.is_access_allowed("foo.bin")); + assert!(context_dir.is_access_allowed("/file/foo.bin")); + assert!(context_dir.is_access_allowed("a/foo.bin")); + assert!(context_dir.is_access_allowed("a/b/foo.bin")); + assert!(context_dir.is_access_allowed("../../file/foo.bin")); + + assert!(!context_dir.is_access_allowed("/tmp/foo.bin")); + assert!(!context_dir.is_access_allowed("../tmp/a/b/foo.bin")); + assert!(!context_dir.is_access_allowed("../foo.bin")); + assert!(!context_dir.is_access_allowed("../../foo.bin")); + assert!(!context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); + } + + #[test] + fn check_filename_allowed_access_with_implicit_relative_user_file_root() { + // ``` + // $ cd /tmp + // $ hurl a/b/test.hurl + // ``` + let current_dir = Path::new("/tmp"); + let file_root = Path::new("a/b"); + let context_dir = ContextDir::new(current_dir, file_root); + assert!(context_dir.is_access_allowed("foo.bin")); + assert!(context_dir.is_access_allowed("c/foo.bin")); // absolute path is /tmp/a/b/c/foo.bin + assert!(context_dir.is_access_allowed("/tmp/a/b/foo.bin")); + assert!(context_dir.is_access_allowed("/tmp/a/b/c/d/foo.bin")); + assert!(context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); + + assert!(!context_dir.is_access_allowed("/tmp/foo.bin")); + } + + #[test] + fn check_filename_allowed_access_with_explicit_relative_user_file_root() { + // ``` + // $ cd /tmp + // $ hurl --file-root ../tmp test.hurl + // ``` + let current_dir = Path::new("/tmp"); + let file_root = Path::new("../tmp"); + let context_dir = ContextDir::new(current_dir, file_root); + assert!(context_dir.is_access_allowed("foo.bin")); + assert!(context_dir.is_access_allowed("/tmp/foo.bin")); + assert!(context_dir.is_access_allowed("a/foo.bin")); + assert!(context_dir.is_access_allowed("a/b/foo.bin")); + assert!(context_dir.is_access_allowed("../tmp/a/b/foo.bin")); + assert!(context_dir.is_access_allowed("../../../tmp/a/b/foo.bin")); + + assert!(!context_dir.is_access_allowed("/file/foo.bin")); + assert!(!context_dir.is_access_allowed("../foo.bin")); + assert!(!context_dir.is_access_allowed("../../foo.bin")); + assert!(!context_dir.is_access_allowed("../../file/foo.bin")); + } + #[test] fn is_descendant_true() { let child = Path::new("/tmp/foo/bar.txt");