diff --git a/crates/nu-command/src/network/http/client.rs b/crates/nu-command/src/network/http/client.rs index d6c7da9d78..e251f08ed2 100644 --- a/crates/nu-command/src/network/http/client.rs +++ b/crates/nu-command/src/network/http/client.rs @@ -5,7 +5,8 @@ use base64::{alphabet, Engine}; use nu_protocol::ast::Call; use nu_protocol::engine::{EngineState, Stack}; use nu_protocol::{ - record, BufferedReader, IntoPipelineData, PipelineData, RawStream, ShellError, Span, Value, + record, BufferedReader, IntoPipelineData, PipelineData, RawStream, ShellError, Span, Spanned, + Value, }; use ureq::{Error, ErrorKind, Request, Response}; @@ -26,8 +27,16 @@ pub enum BodyType { Unknown, } +#[derive(Clone, Copy, PartialEq)] +pub enum RedirectMode { + Follow, + Error, + Manual, +} + pub fn http_client( allow_insecure: bool, + redirect_mode: RedirectMode, engine_state: &EngineState, stack: &mut Stack, ) -> Result { @@ -46,6 +55,10 @@ pub fn http_client( .user_agent("nushell") .tls_connector(std::sync::Arc::new(tls)); + if let RedirectMode::Manual | RedirectMode::Error = redirect_mode { + agent_builder = agent_builder.redirects(0); + } + if let Some(http_proxy) = retrieve_http_proxy_from_env(engine_state, stack) { if let Ok(proxy) = ureq::Proxy::new(http_proxy) { agent_builder = agent_builder.proxy(proxy); @@ -72,6 +85,18 @@ pub fn http_parse_url( Ok((requested_url, url)) } +pub fn http_parse_redirect_mode(mode: Option>) -> Result { + mode.map_or(Ok(RedirectMode::Follow), |v| match &v.item[..] { + "follow" | "f" => Ok(RedirectMode::Follow), + "error" | "e" => Ok(RedirectMode::Error), + "manual" | "m" => Ok(RedirectMode::Manual), + _ => Err(ShellError::TypeMismatch { + err_message: "Invalid redirect handling mode".to_string(), + span: v.span, + }), + }) +} + pub fn response_to_buffer( response: Response, engine_state: &EngineState, @@ -456,6 +481,26 @@ fn transform_response_using_content_type( }; } +pub fn check_response_redirection( + redirect_mode: RedirectMode, + span: Span, + response: &Result, +) -> Result<(), ShellError> { + if let Ok(resp) = response { + if RedirectMode::Error == redirect_mode && (300..400).contains(&resp.status()) { + return Err(ShellError::NetworkFailure { + msg: format!( + "Redirect encountered when redirect handling mode was 'error' ({} {})", + resp.status(), + resp.status_text() + ), + span, + }); + } + } + Ok(()) +} + fn request_handle_response_content( engine_state: &EngineState, stack: &mut Stack, diff --git a/crates/nu-command/src/network/http/delete.rs b/crates/nu-command/src/network/http/delete.rs index fa3dea621e..2724ad7e3f 100644 --- a/crates/nu-command/src/network/http/delete.rs +++ b/crates/nu-command/src/network/http/delete.rs @@ -2,12 +2,13 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value, }; use crate::network::http::client::{ - http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers, - request_handle_response, request_set_timeout, send_request, + check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, + request_add_authorization_header, request_add_custom_headers, request_handle_response, + request_set_timeout, send_request, }; use super::client::RequestFlags; @@ -79,6 +80,11 @@ impl Command for SubCommand { "allow-errors", "do not fail if the server returns an error code", Some('e'), + ).named( + "redirect-mode", + SyntaxShape::String, + "What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').", + Some('R') ) .filter() .category(Category::Network) @@ -150,6 +156,7 @@ struct Arguments { timeout: Option, full: bool, allow_errors: bool, + redirect: Option>, } fn run_delete( @@ -170,6 +177,7 @@ fn run_delete( timeout: call.get_flag(engine_state, stack, "max-time")?, full: call.has_flag("full"), allow_errors: call.has_flag("allow-errors"), + redirect: call.get_flag(engine_state, stack, "redirect-mode")?, }; helper(engine_state, stack, call, args) @@ -186,8 +194,9 @@ fn helper( let span = args.url.span(); let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; + let redirect_mode = http_parse_redirect_mode(args.redirect)?; - let client = http_client(args.insecure, engine_state, stack)?; + let client = http_client(args.insecure, redirect_mode, engine_state, stack)?; let mut request = client.delete(&requested_url); request = request_set_timeout(args.timeout, request)?; @@ -202,6 +211,7 @@ fn helper( allow_errors: args.allow_errors, }; + check_response_redirection(redirect_mode, span, &response)?; request_handle_response( engine_state, stack, diff --git a/crates/nu-command/src/network/http/get.rs b/crates/nu-command/src/network/http/get.rs index b068f7e3af..7666551f51 100644 --- a/crates/nu-command/src/network/http/get.rs +++ b/crates/nu-command/src/network/http/get.rs @@ -2,16 +2,15 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value, }; use crate::network::http::client::{ - http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers, - request_handle_response, request_set_timeout, send_request, + check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, + request_add_authorization_header, request_add_custom_headers, request_handle_response, + request_set_timeout, send_request, RequestFlags, }; -use super::client::RequestFlags; - #[derive(Clone)] pub struct SubCommand; @@ -73,6 +72,12 @@ impl Command for SubCommand { "do not fail if the server returns an error code", Some('e'), ) + .named( + "redirect-mode", + SyntaxShape::String, + "What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').", + Some('R') + ) .filter() .category(Category::Network) } @@ -137,6 +142,7 @@ struct Arguments { timeout: Option, full: bool, allow_errors: bool, + redirect: Option>, } fn run_get( @@ -155,6 +161,7 @@ fn run_get( timeout: call.get_flag(engine_state, stack, "max-time")?, full: call.has_flag("full"), allow_errors: call.has_flag("allow-errors"), + redirect: call.get_flag(engine_state, stack, "redirect-mode")?, }; helper(engine_state, stack, call, args) } @@ -170,8 +177,9 @@ fn helper( let span = args.url.span(); let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; + let redirect_mode = http_parse_redirect_mode(args.redirect)?; - let client = http_client(args.insecure, engine_state, stack)?; + let client = http_client(args.insecure, redirect_mode, engine_state, stack)?; let mut request = client.get(&requested_url); request = request_set_timeout(args.timeout, request)?; @@ -186,6 +194,7 @@ fn helper( allow_errors: args.allow_errors, }; + check_response_redirection(redirect_mode, span, &response)?; request_handle_response( engine_state, stack, diff --git a/crates/nu-command/src/network/http/head.rs b/crates/nu-command/src/network/http/head.rs index c2f3e1a6e2..9d39778be5 100644 --- a/crates/nu-command/src/network/http/head.rs +++ b/crates/nu-command/src/network/http/head.rs @@ -5,12 +5,13 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value, }; use crate::network::http::client::{ - http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers, - request_handle_response_headers, request_set_timeout, send_request, + check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, + request_add_authorization_header, request_add_custom_headers, request_handle_response_headers, + request_set_timeout, send_request, }; #[derive(Clone)] @@ -58,6 +59,11 @@ impl Command for SubCommand { "insecure", "allow insecure server connections when using SSL", Some('k'), + ).named( + "redirect-mode", + SyntaxShape::String, + "What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').", + Some('R') ) .filter() .category(Category::Network) @@ -114,6 +120,7 @@ struct Arguments { user: Option, password: Option, timeout: Option, + redirect: Option>, } fn run_head( @@ -129,6 +136,7 @@ fn run_head( user: call.get_flag(engine_state, stack, "user")?, password: call.get_flag(engine_state, stack, "password")?, timeout: call.get_flag(engine_state, stack, "max-time")?, + redirect: call.get_flag(engine_state, stack, "redirect-mode")?, }; let ctrl_c = engine_state.ctrlc.clone(); @@ -146,8 +154,9 @@ fn helper( ) -> Result { let span = args.url.span(); let (requested_url, _) = http_parse_url(call, span, args.url)?; + let redirect_mode = http_parse_redirect_mode(args.redirect)?; - let client = http_client(args.insecure, engine_state, stack)?; + let client = http_client(args.insecure, redirect_mode, engine_state, stack)?; let mut request = client.head(&requested_url); request = request_set_timeout(args.timeout, request)?; @@ -155,6 +164,7 @@ fn helper( request = request_add_custom_headers(args.headers, request)?; let response = send_request(request, None, None, ctrlc); + check_response_redirection(redirect_mode, span, &response)?; request_handle_response_headers(span, response) } diff --git a/crates/nu-command/src/network/http/options.rs b/crates/nu-command/src/network/http/options.rs index 5f922985c1..1681c75ef6 100644 --- a/crates/nu-command/src/network/http/options.rs +++ b/crates/nu-command/src/network/http/options.rs @@ -10,7 +10,7 @@ use crate::network::http::client::{ request_handle_response, request_set_timeout, send_request, }; -use super::client::RequestFlags; +use super::client::{RedirectMode, RequestFlags}; #[derive(Clone)] pub struct SubCommand; @@ -160,7 +160,7 @@ fn helper( let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; - let client = http_client(args.insecure, engine_state, stack)?; + let client = http_client(args.insecure, RedirectMode::Follow, engine_state, stack)?; let mut request = client.request("OPTIONS", &requested_url); request = request_set_timeout(args.timeout, request)?; diff --git a/crates/nu-command/src/network/http/patch.rs b/crates/nu-command/src/network/http/patch.rs index 20da98c852..29a5c7ce25 100644 --- a/crates/nu-command/src/network/http/patch.rs +++ b/crates/nu-command/src/network/http/patch.rs @@ -2,12 +2,13 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value, }; use crate::network::http::client::{ - http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers, - request_handle_response, request_set_timeout, send_request, + check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, + request_add_authorization_header, request_add_custom_headers, request_handle_response, + request_set_timeout, send_request, }; use super::client::RequestFlags; @@ -75,6 +76,11 @@ impl Command for SubCommand { "allow-errors", "do not fail if the server returns an error code", Some('e'), + ).named( + "redirect-mode", + SyntaxShape::String, + "What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').", + Some('R') ) .filter() .category(Category::Network) @@ -142,6 +148,7 @@ struct Arguments { timeout: Option, full: bool, allow_errors: bool, + redirect: Option>, } fn run_patch( @@ -162,6 +169,7 @@ fn run_patch( timeout: call.get_flag(engine_state, stack, "max-time")?, full: call.has_flag("full"), allow_errors: call.has_flag("allow-errors"), + redirect: call.get_flag(engine_state, stack, "redirect-mode")?, }; helper(engine_state, stack, call, args) @@ -178,8 +186,9 @@ fn helper( let span = args.url.span(); let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; + let redirect_mode = http_parse_redirect_mode(args.redirect)?; - let client = http_client(args.insecure, engine_state, stack)?; + let client = http_client(args.insecure, redirect_mode, engine_state, stack)?; let mut request = client.patch(&requested_url); request = request_set_timeout(args.timeout, request)?; @@ -194,6 +203,7 @@ fn helper( allow_errors: args.allow_errors, }; + check_response_redirection(redirect_mode, span, &response)?; request_handle_response( engine_state, stack, diff --git a/crates/nu-command/src/network/http/post.rs b/crates/nu-command/src/network/http/post.rs index b8d8b11a72..f05297e642 100644 --- a/crates/nu-command/src/network/http/post.rs +++ b/crates/nu-command/src/network/http/post.rs @@ -2,12 +2,13 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value, }; use crate::network::http::client::{ - http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers, - request_handle_response, request_set_timeout, send_request, + check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, + request_add_authorization_header, request_add_custom_headers, request_handle_response, + request_set_timeout, send_request, }; use super::client::RequestFlags; @@ -75,6 +76,11 @@ impl Command for SubCommand { "allow-errors", "do not fail if the server returns an error code", Some('e'), + ).named( + "redirect-mode", + SyntaxShape::String, + "What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').", + Some('R') ) .filter() .category(Category::Network) @@ -140,6 +146,7 @@ struct Arguments { timeout: Option, full: bool, allow_errors: bool, + redirect: Option>, } fn run_post( @@ -160,6 +167,7 @@ fn run_post( timeout: call.get_flag(engine_state, stack, "max-time")?, full: call.has_flag("full"), allow_errors: call.has_flag("allow-errors"), + redirect: call.get_flag(engine_state, stack, "redirect-mode")?, }; helper(engine_state, stack, call, args) @@ -176,8 +184,9 @@ fn helper( let span = args.url.span(); let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; + let redirect_mode = http_parse_redirect_mode(args.redirect)?; - let client = http_client(args.insecure, engine_state, stack)?; + let client = http_client(args.insecure, redirect_mode, engine_state, stack)?; let mut request = client.post(&requested_url); request = request_set_timeout(args.timeout, request)?; @@ -192,6 +201,7 @@ fn helper( allow_errors: args.allow_errors, }; + check_response_redirection(redirect_mode, span, &response)?; request_handle_response( engine_state, stack, diff --git a/crates/nu-command/src/network/http/put.rs b/crates/nu-command/src/network/http/put.rs index 6c3530f915..9eaf7161c8 100644 --- a/crates/nu-command/src/network/http/put.rs +++ b/crates/nu-command/src/network/http/put.rs @@ -2,12 +2,13 @@ use nu_engine::CallExt; use nu_protocol::ast::Call; use nu_protocol::engine::{Command, EngineState, Stack}; use nu_protocol::{ - Category, Example, PipelineData, ShellError, Signature, SyntaxShape, Type, Value, + Category, Example, PipelineData, ShellError, Signature, Spanned, SyntaxShape, Type, Value, }; use crate::network::http::client::{ - http_client, http_parse_url, request_add_authorization_header, request_add_custom_headers, - request_handle_response, request_set_timeout, send_request, + check_response_redirection, http_client, http_parse_redirect_mode, http_parse_url, + request_add_authorization_header, request_add_custom_headers, request_handle_response, + request_set_timeout, send_request, }; use super::client::RequestFlags; @@ -75,6 +76,11 @@ impl Command for SubCommand { "allow-errors", "do not fail if the server returns an error code", Some('e'), + ).named( + "redirect-mode", + SyntaxShape::String, + "What to do when encountering redirects. Default: 'follow'. Valid options: 'follow' ('f'), 'manual' ('m'), 'error' ('e').", + Some('R') ) .filter() .category(Category::Network) @@ -140,6 +146,7 @@ struct Arguments { timeout: Option, full: bool, allow_errors: bool, + redirect: Option>, } fn run_put( @@ -160,6 +167,7 @@ fn run_put( timeout: call.get_flag(engine_state, stack, "max-time")?, full: call.has_flag("full"), allow_errors: call.has_flag("allow-errors"), + redirect: call.get_flag(engine_state, stack, "redirect-mode")?, }; helper(engine_state, stack, call, args) @@ -176,8 +184,9 @@ fn helper( let span = args.url.span(); let ctrl_c = engine_state.ctrlc.clone(); let (requested_url, _) = http_parse_url(call, span, args.url)?; + let redirect_mode = http_parse_redirect_mode(args.redirect)?; - let client = http_client(args.insecure, engine_state, stack)?; + let client = http_client(args.insecure, redirect_mode, engine_state, stack)?; let mut request = client.put(&requested_url); request = request_set_timeout(args.timeout, request)?; @@ -192,6 +201,7 @@ fn helper( allow_errors: args.allow_errors, }; + check_response_redirection(redirect_mode, span, &response)?; request_handle_response( engine_state, stack, diff --git a/crates/nu-command/tests/commands/network/http/delete.rs b/crates/nu-command/tests/commands/network/http/delete.rs index fceab9d423..bc71ed3945 100644 --- a/crates/nu-command/tests/commands/network/http/delete.rs +++ b/crates/nu-command/tests/commands/network/http/delete.rs @@ -38,3 +38,68 @@ fn http_delete_failed_due_to_server_error() { assert!(actual.err.contains("Bad request (400)")) } + +#[test] +fn http_delete_follows_redirect() { + let mut server = Server::new(); + + let _mock = server.mock("GET", "/bar").with_body("bar").create(); + let _mock = server + .mock("DELETE", "/foo") + .with_status(301) + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!("http delete {url}/foo", url = server.url()).as_str() + )); + + assert_eq!(&actual.out, "bar"); +} + +#[test] +fn http_delete_redirect_mode_manual() { + let mut server = Server::new(); + + let _mock = server + .mock("DELETE", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http delete --redirect-mode manual {url}/foo", + url = server.url() + ) + .as_str() + )); + + assert_eq!(&actual.out, "foo"); +} + +#[test] +fn http_delete_redirect_mode_error() { + let mut server = Server::new(); + + let _mock = server + .mock("DELETE", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http delete --redirect-mode error {url}/foo", + url = server.url() + ) + .as_str() + )); + + assert!(&actual.err.contains("nu::shell::network_failure")); + assert!(&actual.err.contains( + "Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)" + )); +} diff --git a/crates/nu-command/tests/commands/network/http/get.rs b/crates/nu-command/tests/commands/network/http/get.rs index d63f3864d3..f7071ae55b 100644 --- a/crates/nu-command/tests/commands/network/http/get.rs +++ b/crates/nu-command/tests/commands/network/http/get.rs @@ -176,6 +176,71 @@ fn http_get_full_response() { assert_eq!(header["value"], "close"); } +#[test] +fn http_get_follows_redirect() { + let mut server = Server::new(); + + let _mock = server.mock("GET", "/bar").with_body("bar").create(); + let _mock = server + .mock("GET", "/foo") + .with_status(301) + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!("http get {url}/foo", url = server.url()).as_str() + )); + + assert_eq!(&actual.out, "bar"); +} + +#[test] +fn http_get_redirect_mode_manual() { + let mut server = Server::new(); + + let _mock = server + .mock("GET", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http get --redirect-mode manual {url}/foo", + url = server.url() + ) + .as_str() + )); + + assert_eq!(&actual.out, "foo"); +} + +#[test] +fn http_get_redirect_mode_error() { + let mut server = Server::new(); + + let _mock = server + .mock("GET", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http get --redirect-mode error {url}/foo", + url = server.url() + ) + .as_str() + )); + + assert!(&actual.err.contains("nu::shell::network_failure")); + assert!(&actual.err.contains( + "Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)" + )); +} + // These tests require network access; they use badssl.com which is a Google-affiliated site for testing various SSL errors. // Revisit this if these tests prove to be flaky or unstable. diff --git a/crates/nu-command/tests/commands/network/http/head.rs b/crates/nu-command/tests/commands/network/http/head.rs index 0866d97e99..f9af678fc5 100644 --- a/crates/nu-command/tests/commands/network/http/head.rs +++ b/crates/nu-command/tests/commands/network/http/head.rs @@ -39,3 +39,75 @@ fn http_head_failed_due_to_server_error() { assert!(actual.err.contains("Bad request (400)")) } + +#[test] +fn http_head_follows_redirect() { + let mut server = Server::new(); + + let _mock = server + .mock("HEAD", "/bar") + .with_header("bar", "bar") + .create(); + let _mock = server + .mock("HEAD", "/foo") + .with_status(301) + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http head {url}/foo | (where name == bar).0.value", + url = server.url() + ) + .as_str() + )); + + assert_eq!(&actual.out, "bar"); +} + +#[test] +fn http_head_redirect_mode_manual() { + let mut server = Server::new(); + + let _mock = server + .mock("HEAD", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http head --redirect-mode manual {url}/foo | (where name == location).0.value", + url = server.url() + ) + .as_str() + )); + + assert_eq!(&actual.out, "/bar"); +} + +#[test] +fn http_head_redirect_mode_error() { + let mut server = Server::new(); + + let _mock = server + .mock("HEAD", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http head --redirect-mode error {url}/foo", + url = server.url() + ) + .as_str() + )); + + assert!(&actual.err.contains("nu::shell::network_failure")); + assert!(&actual.err.contains( + "Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)" + )); +} diff --git a/crates/nu-command/tests/commands/network/http/patch.rs b/crates/nu-command/tests/commands/network/http/patch.rs index 53d8b946e0..4196b304c6 100644 --- a/crates/nu-command/tests/commands/network/http/patch.rs +++ b/crates/nu-command/tests/commands/network/http/patch.rs @@ -76,3 +76,68 @@ fn http_patch_failed_due_to_unexpected_body() { assert!(actual.err.contains("Cannot make request")) } + +#[test] +fn http_patch_follows_redirect() { + let mut server = Server::new(); + + let _mock = server.mock("GET", "/bar").with_body("bar").create(); + let _mock = server + .mock("PATCH", "/foo") + .with_status(301) + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!("http patch {url}/foo patchbody", url = server.url()).as_str() + )); + + assert_eq!(&actual.out, "bar"); +} + +#[test] +fn http_patch_redirect_mode_manual() { + let mut server = Server::new(); + + let _mock = server + .mock("PATCH", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http patch --redirect-mode manual {url}/foo patchbody", + url = server.url() + ) + .as_str() + )); + + assert_eq!(&actual.out, "foo"); +} + +#[test] +fn http_patch_redirect_mode_error() { + let mut server = Server::new(); + + let _mock = server + .mock("PATCH", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http patch --redirect-mode error {url}/foo patchbody", + url = server.url() + ) + .as_str() + )); + + assert!(&actual.err.contains("nu::shell::network_failure")); + assert!(&actual.err.contains( + "Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)" + )); +} diff --git a/crates/nu-command/tests/commands/network/http/post.rs b/crates/nu-command/tests/commands/network/http/post.rs index 7bc1b4d45c..bd13542482 100644 --- a/crates/nu-command/tests/commands/network/http/post.rs +++ b/crates/nu-command/tests/commands/network/http/post.rs @@ -112,3 +112,68 @@ fn http_post_json_list_is_success() { mock.assert(); assert!(actual.out.is_empty()) } + +#[test] +fn http_post_follows_redirect() { + let mut server = Server::new(); + + let _mock = server.mock("GET", "/bar").with_body("bar").create(); + let _mock = server + .mock("POST", "/foo") + .with_status(301) + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!("http post {url}/foo postbody", url = server.url()).as_str() + )); + + assert_eq!(&actual.out, "bar"); +} + +#[test] +fn http_post_redirect_mode_manual() { + let mut server = Server::new(); + + let _mock = server + .mock("POST", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http post --redirect-mode manual {url}/foo postbody", + url = server.url() + ) + .as_str() + )); + + assert_eq!(&actual.out, "foo"); +} + +#[test] +fn http_post_redirect_mode_error() { + let mut server = Server::new(); + + let _mock = server + .mock("POST", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http post --redirect-mode error {url}/foo postbody", + url = server.url() + ) + .as_str() + )); + + assert!(&actual.err.contains("nu::shell::network_failure")); + assert!(&actual.err.contains( + "Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)" + )); +} diff --git a/crates/nu-command/tests/commands/network/http/put.rs b/crates/nu-command/tests/commands/network/http/put.rs index 82d4ea771a..002b1f8632 100644 --- a/crates/nu-command/tests/commands/network/http/put.rs +++ b/crates/nu-command/tests/commands/network/http/put.rs @@ -76,3 +76,68 @@ fn http_put_failed_due_to_unexpected_body() { assert!(actual.err.contains("Cannot make request")) } + +#[test] +fn http_put_follows_redirect() { + let mut server = Server::new(); + + let _mock = server.mock("GET", "/bar").with_body("bar").create(); + let _mock = server + .mock("PUT", "/foo") + .with_status(301) + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!("http put {url}/foo putbody", url = server.url()).as_str() + )); + + assert_eq!(&actual.out, "bar"); +} + +#[test] +fn http_put_redirect_mode_manual() { + let mut server = Server::new(); + + let _mock = server + .mock("PUT", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http put --redirect-mode manual {url}/foo putbody", + url = server.url() + ) + .as_str() + )); + + assert_eq!(&actual.out, "foo"); +} + +#[test] +fn http_put_redirect_mode_error() { + let mut server = Server::new(); + + let _mock = server + .mock("PUT", "/foo") + .with_status(301) + .with_body("foo") + .with_header("Location", "/bar") + .create(); + + let actual = nu!(pipeline( + format!( + "http put --redirect-mode error {url}/foo putbody", + url = server.url() + ) + .as_str() + )); + + assert!(&actual.err.contains("nu::shell::network_failure")); + assert!(&actual.err.contains( + "Redirect encountered when redirect handling mode was 'error' (301 Moved Permanently)" + )); +}