cloud_sdk/pagination/
header_cursor.rs1use core::fmt;
2
3use cloud_sdk_sanitization::{SecretBuffer, sanitize_bytes};
4
5use super::{PaginationCursor, PaginationError, PaginationLimits};
6use crate::buffer::write_u64;
7use crate::operation::OperationId;
8use crate::transport::{
9 HeaderName, HeaderSensitivity, RequestHeader, RequestHeaders, ResponseHeaders,
10};
11
12mod execution;
13pub use execution::{
14 HeaderCursorContinuation, HeaderCursorExecutionError, HeaderCursorNext, HeaderCursorPage,
15 HeaderCursorSession,
16};
17
18#[derive(Clone, Copy)]
20pub struct HeaderCursorPolicy<'a> {
21 operation: OperationId,
22 cursor_request: HeaderName<'a>,
23 size_request: HeaderName<'a>,
24 next_response: HeaderName<'a>,
25 page_size: u64,
26}
27
28impl<'a> HeaderCursorPolicy<'a> {
29 pub fn new(
31 operation: OperationId,
32 cursor_request: &'a str,
33 size_request: &'a str,
34 next_response: &'a str,
35 page_size: u64,
36 ) -> Result<Self, PaginationError> {
37 let cursor_request =
38 HeaderName::new(cursor_request).map_err(|_| PaginationError::InvalidHeaderPolicy)?;
39 let size_request =
40 HeaderName::new(size_request).map_err(|_| PaginationError::InvalidHeaderPolicy)?;
41 let next_response =
42 HeaderName::new(next_response).map_err(|_| PaginationError::InvalidHeaderPolicy)?;
43 RequestHeader::sensitive(cursor_request.as_str(), "cursor")
44 .map_err(|_| PaginationError::InvalidHeaderPolicy)?;
45 RequestHeader::new(size_request.as_str(), "1")
46 .map_err(|_| PaginationError::InvalidHeaderPolicy)?;
47 if page_size == 0 {
48 return Err(PaginationError::PageSizeZero);
49 }
50 if cursor_request == size_request
51 || cursor_request == next_response
52 || size_request == next_response
53 {
54 return Err(PaginationError::InvalidHeaderPolicy);
55 }
56 Ok(Self {
57 operation,
58 cursor_request,
59 size_request,
60 next_response,
61 page_size,
62 })
63 }
64
65 #[must_use]
67 pub const fn operation_id(self) -> OperationId {
68 self.operation
69 }
70
71 #[must_use]
73 pub const fn cursor_request(self) -> HeaderName<'a> {
74 self.cursor_request
75 }
76
77 #[must_use]
79 pub const fn size_request(self) -> HeaderName<'a> {
80 self.size_request
81 }
82
83 #[must_use]
85 pub const fn next_response(self) -> HeaderName<'a> {
86 self.next_response
87 }
88
89 #[must_use]
91 pub const fn page_size(self) -> u64 {
92 self.page_size
93 }
94
95 pub(super) fn with_request_headers<R>(
96 self,
97 cursor: Option<&PaginationCursor<'_>>,
98 decimal_scratch: &mut [u8],
99 inspect: impl FnOnce(RequestHeaders<'_>) -> R,
100 ) -> Result<R, PaginationError> {
101 let mut scratch = SecretBuffer::new(decimal_scratch);
102 let mut len = 0_usize;
103 write_u64(
104 scratch.as_mut_slice(),
105 &mut len,
106 self.page_size,
107 PaginationError::OutputTooSmall,
108 )?;
109 let size = core::str::from_utf8(
110 scratch
111 .as_slice()
112 .get(..len)
113 .ok_or(PaginationError::OutputTooSmall)?,
114 )
115 .map_err(|_| PaginationError::InvalidHeaderState)?;
116 match cursor {
117 None => self.inspect_request_headers(size, None, inspect),
118 Some(cursor) => cursor.with_cursor(|value| {
119 let value =
120 core::str::from_utf8(value).map_err(|_| PaginationError::InvalidHeaderState)?;
121 self.inspect_request_headers(size, Some(value), inspect)
122 }),
123 }
124 }
125
126 pub(super) fn decode_next<'storage>(
127 self,
128 headers: &ResponseHeaders<'_>,
129 transfer_scratch: &mut [u8],
130 destination: &'storage mut [u8],
131 limits: PaginationLimits,
132 ) -> Result<DecodedHeaderCursor<'storage>, PaginationError> {
133 sanitize_bytes(transfer_scratch);
134 sanitize_bytes(destination);
135 let Some(header) = headers.get(self.next_response.as_str()) else {
136 return Ok(DecodedHeaderCursor::Complete);
137 };
138 if header.sensitivity() != HeaderSensitivity::Sensitive {
139 return Err(PaginationError::InsecureHeaderState);
140 }
141 let value = header.value();
142 validate_cursor_value(value, limits)?;
143 let source = transfer_scratch
144 .get_mut(..value.len())
145 .ok_or(PaginationError::OutputTooSmall)?;
146 source.copy_from_slice(value);
147 PaginationCursor::transfer_from(source, destination, limits)
148 .map(DecodedHeaderCursor::Continue)
149 }
150
151 fn inspect_request_headers<R>(
152 self,
153 size: &str,
154 cursor: Option<&str>,
155 inspect: impl FnOnce(RequestHeaders<'_>) -> R,
156 ) -> Result<R, PaginationError> {
157 let size = RequestHeader::new(self.size_request.as_str(), size)
158 .map_err(|_| PaginationError::InvalidHeaderState)?;
159 if let Some(cursor) = cursor {
160 let cursor = RequestHeader::sensitive(self.cursor_request.as_str(), cursor)
161 .map_err(|_| PaginationError::InvalidHeaderState)?;
162 let entries = [size, cursor];
163 let headers =
164 RequestHeaders::new(&entries).map_err(|_| PaginationError::InvalidHeaderPolicy)?;
165 return Ok(inspect(headers));
166 }
167 let entries = [size];
168 let headers =
169 RequestHeaders::new(&entries).map_err(|_| PaginationError::InvalidHeaderPolicy)?;
170 Ok(inspect(headers))
171 }
172}
173
174impl fmt::Debug for HeaderCursorPolicy<'_> {
175 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
176 formatter
177 .debug_struct("HeaderCursorPolicy")
178 .field("operation", &self.operation)
179 .field("cursor_request", &self.cursor_request)
180 .field("size_request", &self.size_request)
181 .field("next_response", &self.next_response)
182 .field("page_size", &self.page_size)
183 .finish()
184 }
185}
186
187pub(super) enum DecodedHeaderCursor<'storage> {
188 Complete,
189 Continue(PaginationCursor<'storage>),
190}
191
192fn validate_cursor_value(value: &[u8], limits: PaginationLimits) -> Result<(), PaginationError> {
193 if value.is_empty() {
194 return Err(PaginationError::MissingState);
195 }
196 if value.len() > limits.max_state_bytes() {
197 return Err(PaginationError::StateTooLong);
198 }
199 if !value.iter().all(|byte| (b' '..=b'~').contains(byte))
200 || value.first() == Some(&b' ')
201 || value.last() == Some(&b' ')
202 {
203 return Err(PaginationError::InvalidHeaderState);
204 }
205 Ok(())
206}