leyline/core/session/execute/
response.rs1use std::sync::Arc;
2use std::time::SystemTime;
3
4use url::Url;
5
6use super::super::decompress::{decompress_and_strip, drain_stream_into_vec};
7use super::journey::Journey;
8use crate::core::Session;
9use crate::core::deadline::Deadline;
10use crate::core::device::{SessionState, StateParts, unix_secs};
11use crate::core::error::Result;
12use crate::core::response::Response;
13use crate::core::transport::TransportResponse;
14use crate::util::lock;
15
16impl Session {
17 pub(super) fn store_cookies(
18 &self,
19 resp_headers: &[(http::HeaderName, http::HeaderValue)],
20 url: &Url,
21 ) {
22 let set_cookies: Vec<&str> = resp_headers
23 .iter()
24 .filter(|(k, _)| *k == "set-cookie")
25 .filter_map(|(_, v)| v.to_str().ok())
26 .collect();
27 if !set_cookies.is_empty() {
28 self.inner
29 .cookie_jar
30 .store_response_cookies(set_cookies.as_slice(), url);
31 }
32 }
33
34 #[must_use]
35 pub fn state(&self) -> SessionState {
36 let now = SystemTime::now();
37 SessionState::from_parts(StateParts {
38 tls_sessions: self.inner.connector.session_cache().export(unix_secs(now)),
39 #[cfg(feature = "http3")]
40 alt_svc: self.inner.pool.alt_svc_entries(),
41 #[cfg(not(feature = "http3"))]
42 alt_svc: Vec::new(),
43 hsts: lock(&self.inner.pool.hsts).export(now),
44 })
45 }
46
47 pub(crate) fn restore_state(&self, parts: &StateParts) {
48 let now = SystemTime::now();
49 self.inner
50 .connector
51 .session_cache()
52 .import(&parts.tls_sessions, unix_secs(now));
53 #[cfg(feature = "http3")]
54 self.inner.pool.restore_alt_svc(&parts.alt_svc);
55 lock(&self.inner.pool.hsts).import(&parts.hsts, now);
56 }
57
58 pub(super) fn note_hsts(
59 &self,
60 url: &Url,
61 resp_headers: &[(http::HeaderName, http::HeaderValue)],
62 ) {
63 if self.inner.tls_trust.accepts_invalid_certs() {
64 return;
65 }
66 let values: Vec<&str> = resp_headers
67 .iter()
68 .filter(|(k, _)| *k == "strict-transport-security")
69 .filter_map(|(_, v)| v.to_str().ok())
70 .collect();
71 if !values.is_empty() {
72 lock(&self.inner.pool.hsts).note(url, &values, SystemTime::now());
73 }
74 }
75
76 pub(super) fn hsts_upgrade(&self, url: &Arc<Url>) -> Arc<Url> {
77 lock(&self.inner.pool.hsts)
78 .upgrade(url, SystemTime::now())
79 .map_or_else(|| Arc::clone(url), Arc::new)
80 }
81
82 #[cfg(feature = "http3")]
83 pub(super) fn note_alt_svc(
84 &self,
85 url: &Url,
86 resp_headers: &[(http::HeaderName, http::HeaderValue)],
87 ) {
88 let owned: Vec<String> = resp_headers
89 .iter()
90 .filter(|(k, _)| *k == "alt-svc")
91 .map(|(_, v)| String::from_utf8_lossy(v.as_bytes()).into_owned())
92 .collect();
93 let fields: Vec<&str> = owned.iter().map(String::as_str).collect();
94 if fields.is_empty() {
95 return;
96 }
97 let age = resp_headers
98 .iter()
99 .find(|(k, _)| *k == "age")
100 .and_then(|(_, v)| v.to_str().ok())
101 .and_then(|v| crate::util::delta_seconds(v.split(',').next().unwrap_or_default()))
102 .map_or(std::time::Duration::ZERO, std::time::Duration::from_secs);
103 if let Some(host) = url.host_str()
104 && let Some(port) = url.port_or_known_default()
105 {
106 self.inner.pool.note_alt_svc(host, port, &fields, age);
107 }
108 }
109
110 pub(super) async fn assemble_response(
111 &self,
112 leg: TransportResponse,
113 journey: Journey,
114 audit_headers: Vec<(String, String)>,
115 stream_response: bool,
116 deadline: &Deadline,
117 ) -> Result<Response> {
118 let TransportResponse {
119 status,
120 headers,
121 trailers,
122 body,
123 final_url,
124 version,
125 tls,
126 ..
127 } = leg;
128 let (body, headers) = self
129 .finalize_response_body(body, headers, stream_response, deadline)
130 .await?;
131 let audited = self.inner.audit_tls.is_some();
132 let mut audit_headers = audit_headers;
133 audit_headers.retain(|(name, value)| crate::core::transport::sent_on(version, name, value));
134 Ok(Response {
135 status,
136 headers: headers.into_iter().collect(),
137 body,
138 url: final_url,
139 redirect_chain: journey.chain,
140 version,
141 trailers,
142 request_headers: audit_headers,
143 tls,
144 request_method: if audited {
145 journey.method
146 } else {
147 String::new()
148 },
149 audit_tls: self.inner.audit_tls.clone(),
150 audit_cache: std::sync::OnceLock::new(),
151 compression: self.inner.compression,
152 timing: journey.timing,
153 attempts: 1,
154 proxy: None,
155 })
156 }
157
158 pub(super) async fn finalize_response_body(
159 &self,
160 resp_body_shape: crate::core::transport::TransportBody,
161 resp_headers: Vec<(http::HeaderName, http::HeaderValue)>,
162 stream_response: bool,
163 deadline: &Deadline,
164 ) -> Result<(
165 crate::core::response::ResponseBody,
166 Vec<(http::HeaderName, http::HeaderValue)>,
167 )> {
168 Ok(match resp_body_shape {
169 crate::core::transport::TransportBody::Streaming(mut bs) if stream_response => {
170 bs.set_read_timeout(deadline.read());
171 bs.set_body_timeout(deadline.body());
172 bs.stop_on(&self.inner.shutdown);
173 bs.watch_end();
174 (
175 crate::core::response::ResponseBody::Streaming(bs),
176 resp_headers,
177 )
178 }
179 crate::core::transport::TransportBody::Streaming(mut bs) => {
180 bs.set_read_timeout(deadline.read());
181 bs.set_body_timeout(deadline.body());
182 let buf = drain_stream_into_vec(bs, self.inner.compression.max_body_size).await?;
183 let (buf, resp_headers) =
184 decompress_and_strip(buf, resp_headers, &self.inner.compression)?;
185 (
186 crate::core::response::ResponseBody::Buffered(buf),
187 resp_headers,
188 )
189 }
190 crate::core::transport::TransportBody::Buffered(buf) => {
191 if stream_response {
192 let mut bs =
193 crate::core::body_stream::BodyStream::from_bytes(bytes::Bytes::from(buf));
194 bs.stop_on(&self.inner.shutdown);
195 bs.watch_end();
196 (
197 crate::core::response::ResponseBody::Streaming(bs),
198 resp_headers,
199 )
200 } else {
201 let (buf, resp_headers) =
202 decompress_and_strip(buf, resp_headers, &self.inner.compression)?;
203 (
204 crate::core::response::ResponseBody::Buffered(buf),
205 resp_headers,
206 )
207 }
208 }
209 })
210 }
211}