1use std::collections::{BTreeMap, VecDeque};
4use std::io::{self, Write};
5use std::pin::Pin;
6use std::task::{Context, Poll};
7
8use aster_forge_xml::{Error as ForgeXmlError, XmlSafetyError, XmlStreamWriter, XmlWriteOptions};
9use bytes::{Bytes, BytesMut};
10use futures::Stream;
11use http::header::CONTENT_TYPE;
12use http::{HeaderValue, StatusCode};
13
14use crate::xml::write_element;
15use crate::xml_response::error_condition_parts;
16use crate::{
17 DavBackendError, DavCancellationToken, DavErrorCondition, DavMultiStatusItem, DavPropStat,
18 DavResponse, DavResponseBody,
19};
20
21const DAV_NAMESPACE: &str = "DAV:";
22const DEFAULT_MAXIMUM_OUTPUT_BYTES: usize = 64 * 1024 * 1024;
23const DEFAULT_MAXIMUM_ITEMS: usize = 100_000;
24const DEFAULT_MAXIMUM_PROPERTIES_PER_ITEM: usize = 4_096;
25const DEFAULT_CHUNK_BYTES: usize = 16 * 1024;
26
27#[derive(Debug, Clone, Copy, PartialEq, Eq)]
29pub struct DavMultiStatusLimits {
30 pub maximum_output_bytes: usize,
31 pub maximum_items: usize,
32 pub maximum_properties_per_item: usize,
33 pub chunk_bytes: usize,
34}
35
36impl DavMultiStatusLimits {
37 #[must_use]
38 pub const fn new(
39 maximum_output_bytes: usize,
40 maximum_items: usize,
41 maximum_properties_per_item: usize,
42 chunk_bytes: usize,
43 ) -> Self {
44 Self {
45 maximum_output_bytes,
46 maximum_items,
47 maximum_properties_per_item,
48 chunk_bytes,
49 }
50 }
51
52 fn validate(self) -> Result<(), DavMultiStatusError> {
53 if self.maximum_output_bytes == 0
54 || self.maximum_items == 0
55 || self.maximum_properties_per_item == 0
56 || self.chunk_bytes == 0
57 {
58 Err(DavMultiStatusError::new(
59 DavMultiStatusErrorKind::InvalidLimits,
60 DavMultiStatusProgress::default(),
61 ))
62 } else {
63 Ok(())
64 }
65 }
66}
67
68impl Default for DavMultiStatusLimits {
69 fn default() -> Self {
70 Self::new(
71 DEFAULT_MAXIMUM_OUTPUT_BYTES,
72 DEFAULT_MAXIMUM_ITEMS,
73 DEFAULT_MAXIMUM_PROPERTIES_PER_ITEM,
74 DEFAULT_CHUNK_BYTES,
75 )
76 }
77}
78
79#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
81pub struct DavMultiStatusProgress {
82 pub response_started: bool,
83 pub emitted_items: usize,
84 pub emitted_bytes: usize,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
89pub enum DavMultiStatusErrorKind {
90 #[error("invalid Multi-Status resource limits")]
91 InvalidLimits,
92 #[error("Multi-Status item limit exceeded")]
93 ItemLimitExceeded,
94 #[error("Multi-Status property limit exceeded")]
95 PropertyLimitExceeded,
96 #[error("invalid Multi-Status response item")]
97 InvalidItem,
98 #[error("Multi-Status output byte limit exceeded")]
99 OutputLimitExceeded,
100 #[error("Multi-Status source was cancelled")]
101 Cancelled,
102 #[error(transparent)]
103 Backend(#[from] DavBackendError),
104 #[error("Multi-Status XML is malformed")]
105 Xml,
106 #[error("Multi-Status output sink failed")]
107 Write,
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
112#[error("{kind}")]
113pub struct DavMultiStatusError {
114 pub kind: DavMultiStatusErrorKind,
115 pub progress: DavMultiStatusProgress,
116}
117
118impl DavMultiStatusError {
119 const fn new(kind: DavMultiStatusErrorKind, progress: DavMultiStatusProgress) -> Self {
120 Self { kind, progress }
121 }
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
126pub enum DavMultiStatusSourceError {
127 #[error(transparent)]
128 Backend(#[from] DavBackendError),
129 #[error("Multi-Status source was cancelled")]
130 Cancelled,
131}
132
133pub type DavMultiStatusStream =
135 Pin<Box<dyn Stream<Item = Result<Bytes, DavMultiStatusError>> + Send + 'static>>;
136
137pub struct DavMultiStatusWriter<W: Write> {
139 writer: XmlStreamWriter<TrackingWriter<W>>,
140 inherited_namespaces: BTreeMap<String, String>,
141 limits: DavMultiStatusLimits,
142 emitted_items: usize,
143}
144
145impl<W: Write> DavMultiStatusWriter<W> {
146 pub fn new(inner: W, limits: DavMultiStatusLimits) -> Result<Self, DavMultiStatusError> {
151 limits.validate()?;
152 let options = XmlWriteOptions::new().max_output_bytes(limits.maximum_output_bytes);
153 let tracking = TrackingWriter::new(inner);
154 let mut writer = XmlStreamWriter::with_options(tracking, options)
155 .map_err(|error| map_writer_error(&error, DavMultiStatusProgress::default()))?;
156 if let Err(error) = writer.start_element("D:multistatus", [("xmlns:D", DAV_NAMESPACE)]) {
157 return Err(map_writer_error(&error, writer_progress(&writer, 0)));
158 }
159 let mut inherited_namespaces = BTreeMap::new();
160 inherited_namespaces.insert("D".to_owned(), DAV_NAMESPACE.to_owned());
161 Ok(Self {
162 writer,
163 inherited_namespaces,
164 limits,
165 emitted_items: 0,
166 })
167 }
168
169 pub fn append(&mut self, item: DavMultiStatusItem) -> Result<(), DavMultiStatusError> {
174 let next_items = self
175 .emitted_items
176 .checked_add(1)
177 .ok_or_else(|| self.error(DavMultiStatusErrorKind::ItemLimitExceeded))?;
178 if next_items > self.limits.maximum_items {
179 return Err(self.error(DavMultiStatusErrorKind::ItemLimitExceeded));
180 }
181 validate_item(&item, self.limits.maximum_properties_per_item)
182 .map_err(|kind| self.error(kind))?;
183
184 if let Err(error) = write_response_item(&mut self.writer, &self.inherited_namespaces, item)
185 {
186 return Err(map_writer_error(&error, self.progress()));
187 }
188 self.emitted_items = next_items;
189 Ok(())
190 }
191
192 #[must_use]
194 pub fn written_bytes(&self) -> usize {
195 self.writer.get_ref().written
196 }
197
198 pub fn get_mut(&mut self) -> &mut W {
203 &mut self.writer.get_mut().inner
204 }
205
206 pub fn finish(mut self) -> Result<W, DavMultiStatusError> {
211 if let Err(error) = self.writer.end_element() {
212 return Err(map_writer_error(&error, self.progress()));
213 }
214 let progress = self.progress();
215 self.writer
216 .finish()
217 .map(|tracking| tracking.inner)
218 .map_err(|error| map_writer_error(&error, progress))
219 }
220
221 fn progress(&self) -> DavMultiStatusProgress {
222 writer_progress(&self.writer, self.emitted_items)
223 }
224
225 fn error(&self, kind: DavMultiStatusErrorKind) -> DavMultiStatusError {
226 DavMultiStatusError::new(kind, self.progress())
227 }
228}
229
230pub fn dav_multistatus_bytes(
236 items: impl IntoIterator<Item = DavMultiStatusItem>,
237 limits: DavMultiStatusLimits,
238) -> Result<Vec<u8>, DavMultiStatusError> {
239 let mut writer = DavMultiStatusWriter::new(Vec::new(), limits)?;
240 for item in items {
241 writer.append(item)?;
242 }
243 writer.finish()
244}
245
246pub fn multistatus_stream_response<S>(
252 source: S,
253 limits: DavMultiStatusLimits,
254) -> Result<DavResponse, DavMultiStatusError>
255where
256 S: Stream<Item = Result<DavMultiStatusItem, DavMultiStatusSourceError>> + Send + 'static,
257{
258 multistatus_stream_response_inner(source, limits, None)
259}
260
261pub fn multistatus_stream_response_with_cancellation<S>(
271 source: S,
272 limits: DavMultiStatusLimits,
273 cancellation: DavCancellationToken,
274) -> Result<DavResponse, DavMultiStatusError>
275where
276 S: Stream<Item = Result<DavMultiStatusItem, DavMultiStatusSourceError>> + Send + 'static,
277{
278 multistatus_stream_response_inner(source, limits, Some(cancellation))
279}
280
281fn multistatus_stream_response_inner<S>(
282 source: S,
283 limits: DavMultiStatusLimits,
284 cancellation: Option<DavCancellationToken>,
285) -> Result<DavResponse, DavMultiStatusError>
286where
287 S: Stream<Item = Result<DavMultiStatusItem, DavMultiStatusSourceError>> + Send + 'static,
288{
289 limits.validate()?;
290 let stream = StreamingMultiStatus::new(Box::pin(source), limits, cancellation);
291 let mut response = DavResponse {
292 status: StatusCode::MULTI_STATUS,
293 headers: http::HeaderMap::new(),
294 body: DavResponseBody::MultiStatus(Box::pin(stream)),
295 };
296 response.headers.insert(
297 CONTENT_TYPE,
298 HeaderValue::from_static("application/xml; charset=utf-8"),
299 );
300 Ok(response)
301}
302
303struct StreamingMultiStatus {
304 source: Pin<
305 Box<
306 dyn Stream<Item = Result<DavMultiStatusItem, DavMultiStatusSourceError>>
307 + Send
308 + 'static,
309 >,
310 >,
311 writer: Option<DavMultiStatusWriter<ChunkBuffer>>,
312 pending: VecDeque<Bytes>,
313 limits: DavMultiStatusLimits,
314 progress: DavMultiStatusProgress,
315 done: bool,
316 cancellation: Option<DavCancellationToken>,
317}
318
319impl StreamingMultiStatus {
320 fn new(
321 source: Pin<
322 Box<
323 dyn Stream<Item = Result<DavMultiStatusItem, DavMultiStatusSourceError>>
324 + Send
325 + 'static,
326 >,
327 >,
328 limits: DavMultiStatusLimits,
329 cancellation: Option<DavCancellationToken>,
330 ) -> Self {
331 Self {
332 source,
333 writer: None,
334 pending: VecDeque::new(),
335 limits,
336 progress: DavMultiStatusProgress::default(),
337 done: false,
338 cancellation,
339 }
340 }
341
342 fn new_writer(&self) -> Result<DavMultiStatusWriter<ChunkBuffer>, DavMultiStatusError> {
343 let buffer = ChunkBuffer::new(
344 self.limits
345 .chunk_bytes
346 .min(self.limits.maximum_output_bytes),
347 );
348 DavMultiStatusWriter::new(buffer, self.limits)
349 }
350
351 fn fail(
352 &mut self,
353 kind: DavMultiStatusErrorKind,
354 ) -> Poll<Option<Result<Bytes, DavMultiStatusError>>> {
355 self.done = true;
356 self.pending.clear();
357 self.writer = None;
358 Poll::Ready(Some(Err(DavMultiStatusError::new(kind, self.progress))))
359 }
360}
361
362impl Drop for StreamingMultiStatus {
363 fn drop(&mut self) {
364 if let Some(cancellation) = &self.cancellation {
365 cancellation.cancel();
366 }
367 }
368}
369
370impl Stream for StreamingMultiStatus {
371 type Item = Result<Bytes, DavMultiStatusError>;
372
373 fn poll_next(mut self: Pin<&mut Self>, context: &mut Context<'_>) -> Poll<Option<Self::Item>> {
374 loop {
375 if let Some(chunk) = self.pending.pop_front() {
376 self.progress.response_started = true;
377 self.progress.emitted_bytes =
378 self.progress.emitted_bytes.saturating_add(chunk.len());
379 return Poll::Ready(Some(Ok(chunk)));
380 }
381 if self.done {
382 return Poll::Ready(None);
383 }
384
385 match self.source.as_mut().poll_next(context) {
386 Poll::Pending => return Poll::Pending,
387 Poll::Ready(Some(Err(DavMultiStatusSourceError::Backend(error)))) => {
388 return self.fail(DavMultiStatusErrorKind::Backend(error));
389 }
390 Poll::Ready(Some(Err(DavMultiStatusSourceError::Cancelled))) => {
391 return self.fail(DavMultiStatusErrorKind::Cancelled);
392 }
393 Poll::Ready(Some(Ok(item))) => {
394 let mut writer = match self.writer.take() {
395 Some(writer) => writer,
396 None => match self.new_writer() {
397 Ok(writer) => writer,
398 Err(mut error) => {
399 error.progress = self.progress;
400 self.done = true;
401 return Poll::Ready(Some(Err(error)));
402 }
403 },
404 };
405 let result = writer.append(item);
406 if let Err(mut error) = result {
407 error.progress.response_started = self.progress.response_started;
408 error.progress.emitted_bytes = self.progress.emitted_bytes;
409 self.done = true;
410 self.writer = None;
411 return Poll::Ready(Some(Err(error)));
412 }
413 self.progress.emitted_items = self.progress.emitted_items.saturating_add(1);
414 self.pending.append(&mut writer.get_mut().take_chunks());
415 self.writer = Some(writer);
416 }
417 Poll::Ready(None) => {
418 let writer = match self.writer.take() {
419 Some(writer) => writer,
420 None => match self.new_writer() {
421 Ok(writer) => writer,
422 Err(mut error) => {
423 error.progress = self.progress;
424 self.done = true;
425 return Poll::Ready(Some(Err(error)));
426 }
427 },
428 };
429 match writer.finish() {
430 Ok(mut buffer) => {
431 self.pending.append(&mut buffer.take_chunks());
432 self.done = true;
433 }
434 Err(mut error) => {
435 error.progress.response_started = self.progress.response_started;
436 error.progress.emitted_bytes = self.progress.emitted_bytes;
437 self.done = true;
438 return Poll::Ready(Some(Err(error)));
439 }
440 }
441 }
442 }
443 }
444 }
445}
446
447fn validate_item(
448 item: &DavMultiStatusItem,
449 maximum_properties: usize,
450) -> Result<(), DavMultiStatusErrorKind> {
451 let property_count = item.propstats.iter().try_fold(0usize, |count, propstat| {
452 count.checked_add(propstat.properties.len())
453 });
454 if property_count.is_none_or(|count| count > maximum_properties) {
455 return Err(DavMultiStatusErrorKind::PropertyLimitExceeded);
456 }
457 if item
458 .status
459 .is_some_and(|status| StatusCode::from_u16(status).is_err())
460 || item
461 .propstats
462 .iter()
463 .any(|propstat| StatusCode::from_u16(propstat.status).is_err())
464 {
465 return Err(DavMultiStatusErrorKind::InvalidItem);
466 }
467 let property_form = item.status.is_none() && !item.propstats.is_empty();
468 let status_form = item.status.is_some() && item.propstats.is_empty();
469 if item.href.is_empty() || !(property_form || status_form) {
470 return Err(DavMultiStatusErrorKind::InvalidItem);
471 }
472 Ok(())
473}
474
475fn write_response_item<W: Write>(
476 writer: &mut XmlStreamWriter<TrackingWriter<W>>,
477 inherited_namespaces: &BTreeMap<String, String>,
478 item: DavMultiStatusItem,
479) -> Result<(), ForgeXmlError> {
480 writer.start("D:response")?;
481 write_text_element(writer, "D:href", &item.href)?;
482 for propstat in &item.propstats {
483 write_propstat(writer, inherited_namespaces, propstat)?;
484 }
485 if let Some(status) = item.status {
486 write_status(writer, status)?;
487 }
488 if let Some(error) = item.error {
489 write_error(writer, &error)?;
490 }
491 writer.end_element()
492}
493
494fn write_propstat<W: Write>(
495 writer: &mut XmlStreamWriter<TrackingWriter<W>>,
496 inherited_namespaces: &BTreeMap<String, String>,
497 propstat: &DavPropStat,
498) -> Result<(), ForgeXmlError> {
499 writer.start("D:propstat")?;
500 writer.start("D:prop")?;
501 for property in &propstat.properties {
502 write_element(writer, property, inherited_namespaces)?;
503 }
504 writer.end_element()?;
505 write_status(writer, propstat.status)?;
506 writer.end_element()
507}
508
509fn write_status<W: Write>(
510 writer: &mut XmlStreamWriter<TrackingWriter<W>>,
511 status: u16,
512) -> Result<(), ForgeXmlError> {
513 let status = StatusCode::from_u16(status)
514 .map_err(|_| ForgeXmlError::InvalidData("invalid HTTP status code".to_owned()))?;
515 let line = format!(
516 "HTTP/1.1 {} {}",
517 status.as_u16(),
518 status.canonical_reason().unwrap_or("Unknown"),
519 );
520 write_text_element(writer, "D:status", &line)
521}
522
523fn write_error<W: Write>(
524 writer: &mut XmlStreamWriter<TrackingWriter<W>>,
525 error: &DavErrorCondition,
526) -> Result<(), ForgeXmlError> {
527 let (name, href) = error_condition_parts(error);
528 writer.start("D:error")?;
529 if let Some(href) = href {
530 writer.start(&format!("D:{name}"))?;
531 write_text_element(writer, "D:href", href)?;
532 writer.end_element()?;
533 } else {
534 writer.empty(&format!("D:{name}"))?;
535 }
536 writer.end_element()
537}
538
539fn write_text_element<W: Write>(
540 writer: &mut XmlStreamWriter<TrackingWriter<W>>,
541 name: &str,
542 text: &str,
543) -> Result<(), ForgeXmlError> {
544 writer.start(name)?;
545 writer.text(text)?;
546 writer.end_element()
547}
548
549fn writer_progress<W: Write>(
550 writer: &XmlStreamWriter<TrackingWriter<W>>,
551 emitted_items: usize,
552) -> DavMultiStatusProgress {
553 let emitted_bytes = writer.get_ref().written;
554 DavMultiStatusProgress {
555 response_started: emitted_bytes != 0,
556 emitted_items,
557 emitted_bytes,
558 }
559}
560
561fn map_writer_error(
562 error: &ForgeXmlError,
563 progress: DavMultiStatusProgress,
564) -> DavMultiStatusError {
565 let kind = match error {
566 ForgeXmlError::Safety(XmlSafetyError::OutputTooLarge) => {
567 DavMultiStatusErrorKind::OutputLimitExceeded
568 }
569 ForgeXmlError::Safety(_) => DavMultiStatusErrorKind::Xml,
570 ForgeXmlError::InvalidXml(_) | ForgeXmlError::InvalidData(_) => {
571 DavMultiStatusErrorKind::Xml
572 }
573 ForgeXmlError::Io(_) => DavMultiStatusErrorKind::Write,
574 };
575 DavMultiStatusError::new(kind, progress)
576}
577
578struct TrackingWriter<W> {
579 inner: W,
580 written: usize,
581}
582
583impl<W> TrackingWriter<W> {
584 const fn new(inner: W) -> Self {
585 Self { inner, written: 0 }
586 }
587}
588
589impl<W: Write> Write for TrackingWriter<W> {
590 fn write(&mut self, buffer: &[u8]) -> io::Result<usize> {
591 let written = self.inner.write(buffer)?;
592 self.written = self
593 .written
594 .checked_add(written)
595 .ok_or_else(|| io::Error::other("Multi-Status byte count overflow"))?;
596 Ok(written)
597 }
598
599 fn flush(&mut self) -> io::Result<()> {
600 self.inner.flush()
601 }
602}
603
604struct ChunkBuffer {
605 chunk_bytes: usize,
606 current: BytesMut,
607 ready: VecDeque<Bytes>,
608}
609
610impl ChunkBuffer {
611 fn new(chunk_bytes: usize) -> Self {
612 Self {
613 chunk_bytes,
614 current: BytesMut::with_capacity(chunk_bytes.min(DEFAULT_CHUNK_BYTES)),
615 ready: VecDeque::new(),
616 }
617 }
618
619 fn take_chunks(&mut self) -> VecDeque<Bytes> {
620 if !self.current.is_empty() {
621 self.ready.push_back(self.current.split().freeze());
622 }
623 std::mem::take(&mut self.ready)
624 }
625}
626
627impl Write for ChunkBuffer {
628 fn write(&mut self, mut buffer: &[u8]) -> io::Result<usize> {
629 let input_len = buffer.len();
630 while !buffer.is_empty() {
631 let remaining = self.chunk_bytes - self.current.len();
632 let take = remaining.min(buffer.len());
633 self.current.extend_from_slice(&buffer[..take]);
634 buffer = &buffer[take..];
635 if self.current.len() == self.chunk_bytes {
636 self.ready.push_back(self.current.split().freeze());
637 }
638 }
639 Ok(input_len)
640 }
641
642 fn flush(&mut self) -> io::Result<()> {
643 Ok(())
644 }
645}
646
647#[cfg(test)]
648mod tests {
649 use super::*;
650
651 #[test]
652 fn status_writer_rejects_an_unvalidated_invalid_code() {
653 let tracking = TrackingWriter::new(Vec::new());
654 let mut writer = XmlStreamWriter::new(tracking).expect("writer");
655 writer.start("root").expect("root");
656 assert!(matches!(
657 write_status(&mut writer, 99),
658 Err(ForgeXmlError::InvalidData(_))
659 ));
660 }
661}