Remove public visibility of mod http.

This commit is contained in:
jcamiel 2023-03-03 13:55:43 +01:00
parent ae4acfbec1
commit 2d1cbcc694
No known key found for this signature in database
GPG Key ID: 07FF11CFD55356CC
24 changed files with 392 additions and 493 deletions

View File

@ -15,7 +15,6 @@
* limitations under the License.
*
*/
use crate::cli::CliError;
use std::fs;
use std::fs::File;

View File

@ -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<String>,
@ -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)

View File

@ -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};

View File

@ -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<PathBuf>`).
/// 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"));
}
}

View File

@ -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;

View File

@ -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&param2=&param3=a%3Db&param4=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(),

View File

@ -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()
}
}
}

View File

@ -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!(

View File

@ -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(
"<html><head><meta charset=\"UTF-8\"></head><body><br></body></html>",
)),
..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#"
<?xml version="1.0"?>
<users>
<user id="1">Bob</user>
<user id="2">Bill</user>
</users>
"#
.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#"
<?xml version="1.0"?>
<users>
<user id="1">Bob</user>
<user id="2">Bill</user>
<user id="3">Bruce</user>
</users>
"#
.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 {

View File

@ -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 {

View File

@ -15,5 +15,149 @@
* limitations under the License.
*
*/
use crate::http::{Header, Method, Param, RequestCookie, RequestSpec, Response};
mod libcurl;
mod runner;
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#"
<?xml version="1.0"?>
<users>
<user id="1">Bob</user>
<user id="2">Bill</user>
</users>
"#
.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#"
<?xml version="1.0"?>
<users>
<user id="1">Bob</user>
<user id="2">Bill</user>
<user id="3">Bruce</user>
</users>
"#
.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(
"<html><head><meta charset=\"UTF-8\"></head><body><br></body></html>",
)),
..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()
}
}

View File

@ -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,

View File

@ -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;

View File

@ -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,

View File

@ -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))),

View File

@ -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,

View File

@ -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

View File

@ -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<String, Value>,
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,

View File

@ -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,

View File

@ -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![

View File

@ -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"),

View File

@ -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`.

View File

@ -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;

View File

@ -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<PathBuf>`).
/// 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");