1use core::fmt;
2
3use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes, sanitize_value};
4
5use crate::Method;
6use crate::authentication::{
7 AsyncAuthenticatedTransport, AuthenticatedRequest, AuthenticationScopePolicy,
8 BlockingAuthenticatedTransport, drive_async_authenticated,
9};
10use crate::operation::OperationId;
11use crate::transport::{
12 BoundTransport, EndpointIdentity, EndpointScheme, RawResponsePolicy, RequestPath,
13 RequestTarget, ResponseWriter, ResponseWriterError, TransportRequest,
14};
15
16use super::{PaginationError, PaginationLimits};
17
18#[derive(Clone, Copy)]
20pub struct ProviderLinkBinding<'a> {
21 endpoint: EndpointIdentity<'a>,
22 method: Method,
23 operation: OperationId,
24 path: RequestPath<'a>,
25}
26
27impl<'a> ProviderLinkBinding<'a> {
28 #[must_use]
30 pub const fn new(
31 endpoint: EndpointIdentity<'a>,
32 method: Method,
33 operation: OperationId,
34 path: RequestPath<'a>,
35 ) -> Self {
36 Self {
37 endpoint,
38 method,
39 operation,
40 path,
41 }
42 }
43}
44
45impl fmt::Debug for ProviderLinkBinding<'_> {
46 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
47 formatter
48 .debug_struct("ProviderLinkBinding")
49 .field("endpoint", &self.endpoint)
50 .field("method", &self.method)
51 .field("operation", &self.operation)
52 .field("path", &"[redacted]")
53 .finish()
54 }
55}
56
57#[derive(Clone, Copy, Eq, PartialEq)]
62pub enum ProviderLinkExecutionError<E> {
63 Pagination(PaginationError),
65 Transport(E),
67 ResponseWriter(ResponseWriterError),
69}
70
71impl<E> fmt::Debug for ProviderLinkExecutionError<E> {
72 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
73 match self {
74 Self::Pagination(error) => formatter.debug_tuple("Pagination").field(error).finish(),
75 Self::Transport(_) => formatter.write_str("Transport([redacted])"),
76 Self::ResponseWriter(error) => formatter
77 .debug_tuple("ResponseWriter")
78 .field(error)
79 .finish(),
80 }
81 }
82}
83
84impl<E> fmt::Display for ProviderLinkExecutionError<E> {
85 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
86 formatter.write_str(match self {
87 Self::Pagination(_) => "provider pagination link validation failed",
88 Self::Transport(_) => "provider pagination link transport failed",
89 Self::ResponseWriter(_) => "provider pagination response transaction failed",
90 })
91 }
92}
93
94impl<E> core::error::Error for ProviderLinkExecutionError<E> {}
95
96pub struct ValidatedProviderLink<'storage, 'endpoint> {
117 target: SecretBuffer<'storage>,
118 target_len: usize,
119 endpoint: EndpointIdentity<'endpoint>,
120 method: Method,
121 operation: OperationId,
122}
123
124impl<'storage, 'endpoint> ValidatedProviderLink<'storage, 'endpoint> {
125 pub fn transfer_from(
132 source: &mut [u8],
133 destination: &'storage mut [u8],
134 binding: ProviderLinkBinding<'endpoint>,
135 limits: PaginationLimits,
136 ) -> Result<Self, PaginationError> {
137 sanitize_bytes(destination);
138 let source = SecretBuffer::new(source);
139 let mut destination = SecretBuffer::new(destination);
140 if source.as_slice().is_empty() {
141 return Err(PaginationError::MissingState);
142 }
143 if source.as_slice().len() > limits.max_state_bytes() {
144 return Err(PaginationError::StateTooLong);
145 }
146 let link = core::str::from_utf8(source.as_slice())
147 .map_err(|_| PaginationError::InvalidProviderLink)?;
148 let raw_target = extract_target(link, binding.endpoint)?;
149 let target = RequestTarget::from_provider_link(raw_target)
150 .map_err(|_| PaginationError::InvalidProviderLink)?;
151 if target.path() != binding.path {
152 return Err(PaginationError::ProviderLinkPathChanged);
153 }
154 let output = destination
155 .as_mut_slice()
156 .get_mut(..raw_target.len())
157 .ok_or(PaginationError::OutputTooSmall)?;
158 output.copy_from_slice(raw_target.as_bytes());
159 Ok(Self {
160 target: destination,
161 target_len: raw_target.len(),
162 endpoint: binding.endpoint,
163 method: binding.method,
164 operation: binding.operation,
165 })
166 }
167
168 pub fn execute_blocking<T>(
173 &self,
174 transport: &T,
175 method: Method,
176 operation: OperationId,
177 authentication: AuthenticationScopePolicy<'_>,
178 response_policy: RawResponsePolicy<'_>,
179 response: &mut ResponseWriter<'_>,
180 ) -> Result<(), ProviderLinkExecutionError<T::Error>>
181 where
182 T: BoundTransport + BlockingAuthenticatedTransport,
183 {
184 let request = self
185 .request_for(transport, method, operation)
186 .map_err(ProviderLinkExecutionError::Pagination)?;
187 transport
188 .send_authenticated(
189 AuthenticatedRequest::new(request, authentication, response_policy),
190 response,
191 )
192 .map_err(ProviderLinkExecutionError::Transport)
193 }
194
195 pub async fn execute_async<'transport, 'request, 'policy, 'writer, T>(
201 &'request self,
202 transport: &'transport T,
203 method: Method,
204 operation: OperationId,
205 authentication: AuthenticationScopePolicy<'policy>,
206 response_policy: RawResponsePolicy<'policy>,
207 response: &'writer mut ResponseWriter<'_>,
208 ) -> Result<(), ProviderLinkExecutionError<T::Error>>
209 where
210 T: BoundTransport + AsyncAuthenticatedTransport,
211 'transport: 'writer,
212 'request: 'writer,
213 'policy: 'writer,
214 {
215 let request = self
216 .request_for(transport, method, operation)
217 .map_err(ProviderLinkExecutionError::Pagination)?;
218 drive_async_authenticated(
219 transport,
220 AuthenticatedRequest::new(request, authentication, response_policy),
221 response,
222 )
223 .await
224 .map_err(|error| match error {
225 crate::transport::AsyncExecutionError::Transport(error) => {
226 ProviderLinkExecutionError::Transport(error)
227 }
228 crate::transport::AsyncExecutionError::Response(error) => {
229 ProviderLinkExecutionError::ResponseWriter(error)
230 }
231 })
232 }
233
234 pub(super) fn request_for<'request, T: BoundTransport>(
235 &'request self,
236 transport: &T,
237 method: Method,
238 operation: OperationId,
239 ) -> Result<TransportRequest<'request>, PaginationError> {
240 let actual = transport
241 .endpoint_identity()
242 .map_err(|_| PaginationError::ProviderLinkAuthorityChanged)?;
243 if actual != self.endpoint {
244 return Err(PaginationError::ProviderLinkAuthorityChanged);
245 }
246 if method != self.method {
247 return Err(PaginationError::ProviderLinkMethodChanged);
248 }
249 if operation != self.operation {
250 return Err(PaginationError::ProviderLinkOperationChanged);
251 }
252 let bytes = self
253 .target
254 .as_slice()
255 .get(..self.target_len)
256 .ok_or(PaginationError::InvalidProviderLink)?;
257 let value =
258 core::str::from_utf8(bytes).map_err(|_| PaginationError::InvalidProviderLink)?;
259 let target = RequestTarget::from_provider_link(value)
260 .map_err(|_| PaginationError::InvalidProviderLink)?;
261 Ok(TransportRequest::new(method, target))
262 }
263}
264
265impl Drop for ValidatedProviderLink<'_, '_> {
266 fn drop(&mut self) {
267 sanitize_value(&mut self.target_len);
268 }
269}
270
271impl fmt::Debug for ValidatedProviderLink<'_, '_> {
272 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
273 formatter
274 .debug_struct("ValidatedProviderLink")
275 .field("target", &"[redacted]")
276 .field("endpoint", &self.endpoint)
277 .field("method", &self.method)
278 .field("operation", &self.operation)
279 .finish()
280 }
281}
282
283fn extract_target<'a>(
284 link: &'a str,
285 endpoint: EndpointIdentity<'_>,
286) -> Result<&'a str, PaginationError> {
287 if link.contains('#') {
288 return Err(PaginationError::ProviderLinkFragment);
289 }
290 if link.starts_with('/') {
291 return Ok(link);
292 }
293 let (scheme, remainder) = if let Some(value) = link.strip_prefix("https://") {
294 (EndpointScheme::Https, value)
295 } else if let Some(value) = link.strip_prefix("http://") {
296 (EndpointScheme::Http, value)
297 } else {
298 return Err(PaginationError::InvalidProviderLink);
299 };
300 if scheme != endpoint.scheme() {
301 return Err(PaginationError::ProviderLinkSchemeChanged);
302 }
303 let path_start = remainder
304 .find('/')
305 .ok_or(PaginationError::InvalidProviderLink)?;
306 let authority = remainder
307 .get(..path_start)
308 .ok_or(PaginationError::InvalidProviderLink)?;
309 if authority.contains('@') {
310 return Err(PaginationError::ProviderLinkUserinfo);
311 }
312 validate_authority(authority, endpoint)?;
313 remainder
314 .get(path_start..)
315 .ok_or(PaginationError::InvalidProviderLink)
316}
317
318fn validate_authority(
319 authority: &str,
320 endpoint: EndpointIdentity<'_>,
321) -> Result<(), PaginationError> {
322 let (host, port) = split_authority(authority, endpoint.scheme())?;
323 let candidate = EndpointIdentity::new(endpoint.scheme(), host, port, endpoint.base_path())
324 .map_err(|_| PaginationError::ProviderLinkAuthorityChanged)?;
325 if candidate != endpoint {
326 return Err(PaginationError::ProviderLinkAuthorityChanged);
327 }
328 Ok(())
329}
330
331fn split_authority(
332 authority: &str,
333 scheme: EndpointScheme,
334) -> Result<(&str, u16), PaginationError> {
335 let default_port = match scheme {
336 EndpointScheme::Http => 80,
337 EndpointScheme::Https => 443,
338 };
339 if authority.starts_with('[') {
340 let close = authority
341 .find(']')
342 .ok_or(PaginationError::ProviderLinkAuthorityChanged)?;
343 let host_end = close
344 .checked_add(1)
345 .ok_or(PaginationError::ProviderLinkAuthorityChanged)?;
346 let host = authority
347 .get(..host_end)
348 .ok_or(PaginationError::ProviderLinkAuthorityChanged)?;
349 let suffix = authority
350 .get(host_end..)
351 .ok_or(PaginationError::ProviderLinkAuthorityChanged)?;
352 let port = if suffix.is_empty() {
353 default_port
354 } else {
355 parse_port(
356 suffix
357 .strip_prefix(':')
358 .ok_or(PaginationError::ProviderLinkAuthorityChanged)?,
359 )?
360 };
361 return Ok((host, port));
362 }
363 if let Some((host, port)) = authority.rsplit_once(':') {
364 if host.contains(':') {
365 return Err(PaginationError::ProviderLinkAuthorityChanged);
366 }
367 Ok((host, parse_port(port)?))
368 } else {
369 Ok((authority, default_port))
370 }
371}
372
373fn parse_port(value: &str) -> Result<u16, PaginationError> {
374 if value.is_empty() || !value.bytes().all(|byte| byte.is_ascii_digit()) {
375 return Err(PaginationError::ProviderLinkAuthorityChanged);
376 }
377 value
378 .parse::<u16>()
379 .ok()
380 .filter(|port| *port != 0)
381 .ok_or(PaginationError::ProviderLinkAuthorityChanged)
382}