mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-23 09:18:30 +02:00
Add redirect drop metadata and warning UI (#418)
This commit is contained in:
@@ -30,6 +30,8 @@ pub enum HttpResponseEvent {
|
||||
url: String,
|
||||
status: u16,
|
||||
behavior: RedirectBehavior,
|
||||
dropped_body: bool,
|
||||
dropped_headers: Vec<String>,
|
||||
},
|
||||
SendUrl {
|
||||
method: String,
|
||||
@@ -67,12 +69,28 @@ impl Display for HttpResponseEvent {
|
||||
match self {
|
||||
HttpResponseEvent::Setting(name, value) => write!(f, "* Setting {}={}", name, value),
|
||||
HttpResponseEvent::Info(s) => write!(f, "* {}", s),
|
||||
HttpResponseEvent::Redirect { url, status, behavior } => {
|
||||
HttpResponseEvent::Redirect {
|
||||
url,
|
||||
status,
|
||||
behavior,
|
||||
dropped_body,
|
||||
dropped_headers,
|
||||
} => {
|
||||
let behavior_str = match behavior {
|
||||
RedirectBehavior::Preserve => "preserve",
|
||||
RedirectBehavior::DropBody => "drop body",
|
||||
};
|
||||
write!(f, "* Redirect {} -> {} ({})", status, url, behavior_str)
|
||||
let body_str = if *dropped_body { ", body dropped" } else { "" };
|
||||
let headers_str = if dropped_headers.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
format!(", headers dropped: {}", dropped_headers.join(", "))
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"* Redirect {} -> {} ({}{}{})",
|
||||
status, url, behavior_str, body_str, headers_str
|
||||
)
|
||||
}
|
||||
HttpResponseEvent::SendUrl {
|
||||
method,
|
||||
@@ -130,13 +148,21 @@ impl From<HttpResponseEvent> for yaak_models::models::HttpResponseEventData {
|
||||
match event {
|
||||
HttpResponseEvent::Setting(name, value) => D::Setting { name, value },
|
||||
HttpResponseEvent::Info(message) => D::Info { message },
|
||||
HttpResponseEvent::Redirect { url, status, behavior } => D::Redirect {
|
||||
HttpResponseEvent::Redirect {
|
||||
url,
|
||||
status,
|
||||
behavior,
|
||||
dropped_body,
|
||||
dropped_headers,
|
||||
} => D::Redirect {
|
||||
url,
|
||||
status,
|
||||
behavior: match behavior {
|
||||
RedirectBehavior::Preserve => "preserve".to_string(),
|
||||
RedirectBehavior::DropBody => "drop_body".to_string(),
|
||||
},
|
||||
dropped_body,
|
||||
dropped_headers,
|
||||
},
|
||||
HttpResponseEvent::SendUrl {
|
||||
method,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
use crate::cookies::CookieStore;
|
||||
use crate::error::Result;
|
||||
use crate::sender::{HttpResponse, HttpResponseEvent, HttpSender, RedirectBehavior};
|
||||
use crate::types::SendableHttpRequest;
|
||||
use crate::types::{SendableBody, SendableHttpRequest};
|
||||
use log::debug;
|
||||
use tokio::sync::mpsc;
|
||||
use tokio::sync::watch::Receiver;
|
||||
@@ -87,6 +87,11 @@ impl<S: HttpSender> HttpTransaction<S> {
|
||||
};
|
||||
|
||||
// Build request for this iteration
|
||||
let preserved_body = match ¤t_body {
|
||||
Some(SendableBody::Bytes(b)) => Some(SendableBody::Bytes(b.clone())),
|
||||
_ => None,
|
||||
};
|
||||
let request_had_body = current_body.is_some();
|
||||
let req = SendableHttpRequest {
|
||||
url: current_url.clone(),
|
||||
method: current_method.clone(),
|
||||
@@ -182,8 +187,6 @@ impl<S: HttpSender> HttpTransaction<S> {
|
||||
format!("{}/{}", base_path, location)
|
||||
};
|
||||
|
||||
Self::remove_sensitive_headers(&mut current_headers, &previous_url, ¤t_url);
|
||||
|
||||
// Determine redirect behavior based on status code and method
|
||||
let behavior = if status == 303 {
|
||||
// 303 See Other always changes to GET
|
||||
@@ -197,11 +200,8 @@ impl<S: HttpSender> HttpTransaction<S> {
|
||||
RedirectBehavior::Preserve
|
||||
};
|
||||
|
||||
send_event(HttpResponseEvent::Redirect {
|
||||
url: current_url.clone(),
|
||||
status,
|
||||
behavior: behavior.clone(),
|
||||
});
|
||||
let mut dropped_headers =
|
||||
Self::remove_sensitive_headers(&mut current_headers, &previous_url, ¤t_url);
|
||||
|
||||
// Handle method changes for certain redirect codes
|
||||
if matches!(behavior, RedirectBehavior::DropBody) {
|
||||
@@ -211,13 +211,40 @@ impl<S: HttpSender> HttpTransaction<S> {
|
||||
// Remove content-related headers
|
||||
current_headers.retain(|h| {
|
||||
let name_lower = h.0.to_lowercase();
|
||||
!name_lower.starts_with("content-") && name_lower != "transfer-encoding"
|
||||
let should_drop =
|
||||
name_lower.starts_with("content-") || name_lower == "transfer-encoding";
|
||||
if should_drop {
|
||||
Self::push_header_if_missing(&mut dropped_headers, &h.0);
|
||||
}
|
||||
!should_drop
|
||||
});
|
||||
}
|
||||
|
||||
// Reset body for next iteration (since it was moved in the send call)
|
||||
// For redirects that change method to GET or for all redirects since body was consumed
|
||||
current_body = None;
|
||||
// Restore body for Preserve redirects (307/308), drop for others.
|
||||
// Stream bodies can't be replayed (same limitation as reqwest).
|
||||
current_body = if matches!(behavior, RedirectBehavior::Preserve) {
|
||||
if request_had_body && preserved_body.is_none() {
|
||||
// Stream body was consumed and can't be replayed (same as reqwest)
|
||||
return Err(crate::error::Error::RequestError(
|
||||
"Cannot follow redirect: request body was a stream and cannot be resent"
|
||||
.to_string(),
|
||||
));
|
||||
}
|
||||
preserved_body
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Body was dropped if the request had one but we can't resend it
|
||||
let dropped_body = request_had_body && current_body.is_none();
|
||||
|
||||
send_event(HttpResponseEvent::Redirect {
|
||||
url: current_url.clone(),
|
||||
status,
|
||||
behavior: behavior.clone(),
|
||||
dropped_body,
|
||||
dropped_headers,
|
||||
});
|
||||
|
||||
redirect_count += 1;
|
||||
}
|
||||
@@ -231,7 +258,8 @@ impl<S: HttpSender> HttpTransaction<S> {
|
||||
headers: &mut Vec<(String, String)>,
|
||||
previous_url: &str,
|
||||
next_url: &str,
|
||||
) {
|
||||
) -> Vec<String> {
|
||||
let mut dropped_headers = Vec::new();
|
||||
let previous_host = Url::parse(previous_url).ok().and_then(|u| {
|
||||
u.host_str().map(|h| format!("{}:{}", h, u.port_or_known_default().unwrap_or(0)))
|
||||
});
|
||||
@@ -241,13 +269,24 @@ impl<S: HttpSender> HttpTransaction<S> {
|
||||
if previous_host != next_host {
|
||||
headers.retain(|h| {
|
||||
let name_lower = h.0.to_lowercase();
|
||||
name_lower != "authorization"
|
||||
&& name_lower != "cookie"
|
||||
&& name_lower != "cookie2"
|
||||
&& name_lower != "proxy-authorization"
|
||||
&& name_lower != "www-authenticate"
|
||||
let should_drop = name_lower == "authorization"
|
||||
|| name_lower == "cookie"
|
||||
|| name_lower == "cookie2"
|
||||
|| name_lower == "proxy-authorization"
|
||||
|| name_lower == "www-authenticate";
|
||||
if should_drop {
|
||||
Self::push_header_if_missing(&mut dropped_headers, &h.0);
|
||||
}
|
||||
!should_drop
|
||||
});
|
||||
}
|
||||
dropped_headers
|
||||
}
|
||||
|
||||
fn push_header_if_missing(headers: &mut Vec<String>, name: &str) {
|
||||
if !headers.iter().any(|h| h.eq_ignore_ascii_case(name)) {
|
||||
headers.push(name.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if a status code indicates a redirect
|
||||
|
||||
Reference in New Issue
Block a user