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