1use core::fmt;
36
37use alloc::{
38 boxed::Box,
39 collections::BTreeSet,
40 format,
41 string::{String, ToString},
42 vec::Vec,
43};
44
45use std::io::{self, Read, Write};
46
47#[cfg(any(
48 feature = "rustls-aws",
49 feature = "rustls-ring",
50 feature = "native-tls"
51))]
52use pimalaya_stream::{std::stream::StreamStd, tls::Tls};
53use thiserror::Error;
54use url::Url;
55
56use crate::{
57 coroutine::*,
58 rfc4791::{
59 calendar::{
60 Calendar, create::CreateCalendar, delete::DeleteCalendar, home_set::CalendarHomeSet,
61 list::ListCalendars, update::UpdateCalendar,
62 },
63 item::{
64 create::{CreateItem, CreateItemOk},
65 delete::DeleteItem,
66 list::{ItemEntry, ListItems},
67 read::{ItemBody, ReadItem},
68 update::{UpdateItem, UpdateItemOk},
69 },
70 },
71 rfc4918::{
72 GETETAG, WebdavAuth, coroutine::WebdavRedirectYield,
73 follow_redirects::FollowRedirectsError, send::SendError,
74 },
75 rfc5397::current_user_principal::CurrentUserPrincipal,
76 rfc6352::{
77 addressbook::{
78 Addressbook, create::CreateAddressbook, delete::DeleteAddressbook,
79 home_set::AddressbookHomeSet, list::ListAddressbooks, update::UpdateAddressbook,
80 },
81 card::{
82 CardEntry, CardRef,
83 create::{CreateCard, CreateCardOk},
84 delete::DeleteCard,
85 enumerate::EnumCards,
86 list::ListCards,
87 multiget::MultigetCards,
88 read::{CardBody, ReadCard},
89 update::{UpdateCard, UpdateCardOk},
90 },
91 },
92 rfc6578::sync_collection::{SyncCollection, SyncCollectionError, SyncDelta},
93};
94
95const READ_BUFFER_SIZE: usize = 16 * 1024;
96
97const DEFAULT_USER_AGENT: &str = concat!("io-webdav/", env!("CARGO_PKG_VERSION"));
98
99#[derive(Debug, Error)]
101pub enum WebdavClientStdError {
102 #[error(transparent)]
104 Send(#[from] SendError),
105 #[error(transparent)]
107 FollowRedirects(#[from] FollowRedirectsError),
108 #[error(transparent)]
110 SyncCollection(#[from] SyncCollectionError),
111
112 #[error(transparent)]
114 Io(#[from] io::Error),
115
116 #[cfg(any(
118 feature = "rustls-aws",
119 feature = "rustls-ring",
120 feature = "native-tls"
121 ))]
122 #[error(transparent)]
123 Tls(#[from] anyhow::Error),
124 #[cfg(any(
126 feature = "rustls-aws",
127 feature = "rustls-ring",
128 feature = "native-tls"
129 ))]
130 #[error("WebDAV URL `{0}` has no host")]
131 UrlMissingHost(String),
132 #[cfg(any(
134 feature = "rustls-aws",
135 feature = "rustls-ring",
136 feature = "native-tls"
137 ))]
138 #[error("WebDAV URL `{0}` has unsupported scheme `{1}` (expected `http` or `https`)")]
139 UrlUnsupportedScheme(String, String),
140
141 #[error("WebDAV server redirected to `{0}` during a non-redirectable operation")]
144 UnexpectedRedirect(Url),
145
146 #[error("WebDAV client missing principal URL; call `current_user_principal` first")]
149 MissingPrincipal,
150 #[error("WebDAV client missing calendar home-set; call `calendar_home_set` first")]
153 MissingCalendarHomeSet,
154 #[error("WebDAV client missing addressbook home-set; call `addressbook_home_set` first")]
157 MissingAddressbookHomeSet,
158}
159
160pub struct WebdavClientStd {
162 pub stream: Box<dyn WebdavStream>,
166
167 auth: WebdavAuth,
168
169 pub base_url: Url,
171
172 pub user_agent: String,
174
175 pub principal_url: Option<Url>,
177
178 pub calendar_home_set: Option<Url>,
180
181 pub addressbook_home_set: Option<Url>,
183}
184
185impl fmt::Debug for WebdavClientStd {
186 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
187 f.debug_struct("WebdavClientStd")
188 .field("base_url", &self.base_url.as_str())
189 .field("user_agent", &self.user_agent)
190 .field(
191 "principal_url",
192 &self.principal_url.as_ref().map(Url::as_str),
193 )
194 .field(
195 "calendar_home_set",
196 &self.calendar_home_set.as_ref().map(Url::as_str),
197 )
198 .field(
199 "addressbook_home_set",
200 &self.addressbook_home_set.as_ref().map(Url::as_str),
201 )
202 .finish_non_exhaustive()
203 }
204}
205
206impl WebdavClientStd {
207 pub fn new<S: Read + Write + Send + 'static>(
210 stream: S,
211 auth: WebdavAuth,
212 base_url: Url,
213 ) -> Self {
214 Self {
215 stream: Box::new(stream),
216 auth,
217 base_url,
218 user_agent: DEFAULT_USER_AGENT.to_string(),
219 principal_url: None,
220 calendar_home_set: None,
221 addressbook_home_set: None,
222 }
223 }
224
225 pub fn from_parts<S: Read + Write + Send + 'static>(
228 stream: S,
229 auth: WebdavAuth,
230 base_url: Url,
231 principal_url: Option<Url>,
232 calendar_home_set: Option<Url>,
233 addressbook_home_set: Option<Url>,
234 ) -> Self {
235 Self {
236 stream: Box::new(stream),
237 auth,
238 base_url,
239 user_agent: DEFAULT_USER_AGENT.to_string(),
240 principal_url,
241 calendar_home_set,
242 addressbook_home_set,
243 }
244 }
245
246 #[cfg(any(
250 feature = "rustls-aws",
251 feature = "rustls-ring",
252 feature = "native-tls"
253 ))]
254 pub fn connect(url: &Url, tls: &Tls, auth: WebdavAuth) -> Result<Self, WebdavClientStdError> {
255 let host = url
256 .host_str()
257 .ok_or_else(|| WebdavClientStdError::UrlMissingHost(url.to_string()))?;
258
259 let stream = match url.scheme() {
260 "http" => StreamStd::connect_tcp(host, url.port().unwrap_or(80))?,
261 "https" => StreamStd::connect_tls(host, url.port().unwrap_or(443), tls)?,
262 scheme => {
263 return Err(WebdavClientStdError::UrlUnsupportedScheme(
264 url.to_string(),
265 scheme.to_string(),
266 ));
267 }
268 };
269
270 Ok(Self::new(stream, auth, url.clone()))
271 }
272
273 pub fn set_stream<S: Read + Write + Send + 'static>(&mut self, stream: S) {
276 self.stream = Box::new(stream);
277 }
278
279 pub fn auth(&self) -> &WebdavAuth {
281 &self.auth
282 }
283
284 fn run<C, T, E>(&mut self, mut coroutine: C) -> Result<T, WebdavClientStdError>
288 where
289 C: WebdavCoroutine<Yield = WebdavYield, Return = Result<T, E>>,
290 E: Into<WebdavClientStdError>,
291 {
292 let mut buf = [0u8; READ_BUFFER_SIZE];
293 let mut arg: Option<&[u8]> = None;
294
295 let ret = loop {
296 match coroutine.resume(arg.take()) {
297 WebdavCoroutineState::Complete(ret) => break ret,
298 WebdavCoroutineState::Yielded(yielded) => {
299 let n = pump(&mut *self.stream, &mut buf, yielded)?;
300 arg = n.map(|n| &buf[..n]);
301 }
302 }
303 };
304
305 ret.map_err(Into::into)
306 }
307
308 fn run_redirect(
318 &mut self,
319 coroutine: &mut dyn WebdavCoroutine<
320 Yield = WebdavRedirectYield,
321 Return = Result<Option<Url>, FollowRedirectsError>,
322 >,
323 ) -> Result<Option<Url>, WebdavClientStdError> {
324 let mut buf = [0u8; READ_BUFFER_SIZE];
325 let mut arg: Option<&[u8]> = None;
326
327 loop {
328 match coroutine.resume(arg.take()) {
329 WebdavCoroutineState::Complete(Ok(url)) => return Ok(url),
330 WebdavCoroutineState::Complete(Err(err)) => return Err(err.into()),
331 WebdavCoroutineState::Yielded(WebdavRedirectYield::WantsRead) => {
332 let n = self.stream.read(&mut buf)?;
333 arg = Some(&buf[..n]);
334 }
335 WebdavCoroutineState::Yielded(WebdavRedirectYield::WantsWrite(bytes)) => {
336 self.stream.write_all(&bytes)?;
337 arg = None;
338 }
339 WebdavCoroutineState::Yielded(WebdavRedirectYield::WantsRedirect {
340 url, ..
341 }) => {
342 return Err(WebdavClientStdError::UnexpectedRedirect(url));
343 }
344 }
345 }
346 }
347
348 pub fn current_user_principal(&mut self) -> Result<Url, WebdavClientStdError> {
356 if let Some(url) = &self.principal_url {
357 return Ok(url.clone());
358 }
359
360 let mut coroutine = CurrentUserPrincipal::new(&self.base_url, &self.auth, &self.user_agent);
361 let url = self.run_redirect(&mut coroutine)?;
362 let url = url.ok_or(WebdavClientStdError::MissingPrincipal)?;
363
364 self.principal_url = Some(url.clone());
365 Ok(url)
366 }
367
368 pub fn calendar_home_set(&mut self) -> Result<Url, WebdavClientStdError> {
377 if let Some(url) = &self.calendar_home_set {
378 return Ok(url.clone());
379 }
380
381 let principal = self.current_user_principal()?;
382 let path = principal.path().to_string();
383
384 let mut coroutine =
385 CalendarHomeSet::new(&self.base_url, &self.auth, &self.user_agent, &path);
386 let url = self.run_redirect(&mut coroutine)?;
387 let url = url.ok_or(WebdavClientStdError::MissingCalendarHomeSet)?;
388
389 self.calendar_home_set = Some(url.clone());
390 Ok(url)
391 }
392
393 pub fn list_calendars(&mut self) -> Result<BTreeSet<Calendar>, WebdavClientStdError> {
398 let home = self
399 .calendar_home_set
400 .as_ref()
401 .ok_or(WebdavClientStdError::MissingCalendarHomeSet)?;
402 let path = home.path().to_string();
403
404 let coroutine = ListCalendars::new(&self.base_url, &self.auth, &self.user_agent, &path);
405 self.run(coroutine)
406 }
407
408 pub fn create_calendar(&mut self, calendar: &Calendar) -> Result<(), WebdavClientStdError> {
413 let home = self
414 .calendar_home_set
415 .as_ref()
416 .ok_or(WebdavClientStdError::MissingCalendarHomeSet)?;
417 let path = home.path().to_string();
418
419 let coroutine = CreateCalendar::new(
420 &self.base_url,
421 &self.auth,
422 &self.user_agent,
423 &path,
424 calendar,
425 );
426 self.run(coroutine).map(|_| ())
427 }
428
429 pub fn update_calendar(&mut self, calendar: &Calendar) -> Result<(), WebdavClientStdError> {
431 let home = self
432 .calendar_home_set
433 .as_ref()
434 .ok_or(WebdavClientStdError::MissingCalendarHomeSet)?;
435 let path = home.path().to_string();
436
437 let coroutine = UpdateCalendar::new(
438 &self.base_url,
439 &self.auth,
440 &self.user_agent,
441 &path,
442 calendar,
443 );
444 self.run(coroutine)
445 }
446
447 pub fn delete_calendar(&mut self, calendar_id: &str) -> Result<(), WebdavClientStdError> {
449 let home = self
450 .calendar_home_set
451 .as_ref()
452 .ok_or(WebdavClientStdError::MissingCalendarHomeSet)?;
453 let path = home.path().to_string();
454
455 let coroutine = DeleteCalendar::new(
456 &self.base_url,
457 &self.auth,
458 &self.user_agent,
459 &path,
460 calendar_id,
461 );
462 self.run(coroutine).map(|_| ())
463 }
464
465 pub fn list_items(
470 &mut self,
471 calendar_id: &str,
472 comp_filter: &str,
473 ) -> Result<BTreeSet<ItemEntry>, WebdavClientStdError> {
474 let path = calendar_path(self.calendar_home_set.as_ref(), calendar_id)?;
475 let coroutine = ListItems::new(
476 &self.base_url,
477 &self.auth,
478 &self.user_agent,
479 &path,
480 comp_filter,
481 );
482 self.run(coroutine)
483 }
484
485 pub fn read_item(
488 &mut self,
489 calendar_id: &str,
490 item_id: &str,
491 ) -> Result<ItemBody, WebdavClientStdError> {
492 let path = calendar_path(self.calendar_home_set.as_ref(), calendar_id)?;
493 let coroutine = ReadItem::new(&self.base_url, &self.auth, &self.user_agent, &path, item_id);
494 self.run(coroutine)
495 }
496
497 pub fn create_item(
499 &mut self,
500 calendar_id: &str,
501 id: &str,
502 ical: Vec<u8>,
503 ) -> Result<CreateItemOk, WebdavClientStdError> {
504 let path = calendar_path(self.calendar_home_set.as_ref(), calendar_id)?;
505 let coroutine = CreateItem::new(
506 &self.base_url,
507 &self.auth,
508 &self.user_agent,
509 &path,
510 id,
511 ical,
512 );
513 self.run(coroutine)
514 }
515
516 pub fn update_item(
518 &mut self,
519 calendar_id: &str,
520 id: &str,
521 ical: Vec<u8>,
522 if_match: Option<&str>,
523 ) -> Result<UpdateItemOk, WebdavClientStdError> {
524 let path = calendar_path(self.calendar_home_set.as_ref(), calendar_id)?;
525 let coroutine = UpdateItem::new(
526 &self.base_url,
527 &self.auth,
528 &self.user_agent,
529 &path,
530 id,
531 ical,
532 if_match,
533 );
534 self.run(coroutine)
535 }
536
537 pub fn delete_item(
539 &mut self,
540 calendar_id: &str,
541 item_id: &str,
542 if_match: Option<&str>,
543 ) -> Result<(), WebdavClientStdError> {
544 let path = calendar_path(self.calendar_home_set.as_ref(), calendar_id)?;
545 let coroutine = DeleteItem::new(
546 &self.base_url,
547 &self.auth,
548 &self.user_agent,
549 &path,
550 item_id,
551 if_match,
552 );
553 self.run(coroutine).map(|_| ())
554 }
555
556 pub fn addressbook_home_set(&mut self) -> Result<Url, WebdavClientStdError> {
563 if let Some(url) = &self.addressbook_home_set {
564 return Ok(url.clone());
565 }
566
567 let principal = self.current_user_principal()?;
568 let path = principal.path().to_string();
569
570 let mut coroutine =
571 AddressbookHomeSet::new(&self.base_url, &self.auth, &self.user_agent, &path);
572 let url = self.run_redirect(&mut coroutine)?;
573 let url = url.ok_or(WebdavClientStdError::MissingAddressbookHomeSet)?;
574
575 self.addressbook_home_set = Some(url.clone());
576 Ok(url)
577 }
578
579 pub fn list_addressbooks(&mut self) -> Result<BTreeSet<Addressbook>, WebdavClientStdError> {
584 let home = self
585 .addressbook_home_set
586 .as_ref()
587 .ok_or(WebdavClientStdError::MissingAddressbookHomeSet)?;
588 let path = home.path().to_string();
589
590 let coroutine = ListAddressbooks::new(&self.base_url, &self.auth, &self.user_agent, &path);
591 self.run(coroutine)
592 }
593
594 pub fn create_addressbook(
599 &mut self,
600 addressbook: &Addressbook,
601 ) -> Result<(), WebdavClientStdError> {
602 let home = self
603 .addressbook_home_set
604 .as_ref()
605 .ok_or(WebdavClientStdError::MissingAddressbookHomeSet)?;
606 let path = home.path().to_string();
607
608 let coroutine = CreateAddressbook::new(
609 &self.base_url,
610 &self.auth,
611 &self.user_agent,
612 &path,
613 addressbook,
614 );
615 self.run(coroutine).map(|_| ())
616 }
617
618 pub fn update_addressbook(
620 &mut self,
621 addressbook: &Addressbook,
622 ) -> Result<(), WebdavClientStdError> {
623 let home = self
624 .addressbook_home_set
625 .as_ref()
626 .ok_or(WebdavClientStdError::MissingAddressbookHomeSet)?;
627 let path = home.path().to_string();
628
629 let coroutine = UpdateAddressbook::new(
630 &self.base_url,
631 &self.auth,
632 &self.user_agent,
633 &path,
634 addressbook,
635 );
636 self.run(coroutine)
637 }
638
639 pub fn delete_addressbook(&mut self, addressbook_id: &str) -> Result<(), WebdavClientStdError> {
641 let home = self
642 .addressbook_home_set
643 .as_ref()
644 .ok_or(WebdavClientStdError::MissingAddressbookHomeSet)?;
645 let path = home.path().to_string();
646
647 let coroutine = DeleteAddressbook::new(
648 &self.base_url,
649 &self.auth,
650 &self.user_agent,
651 &path,
652 addressbook_id,
653 );
654 self.run(coroutine).map(|_| ())
655 }
656
657 pub fn list_cards(
659 &mut self,
660 addressbook_id: &str,
661 ) -> Result<BTreeSet<CardEntry>, WebdavClientStdError> {
662 let path = addressbook_path(self.addressbook_home_set.as_ref(), addressbook_id)?;
663 let coroutine = ListCards::new(&self.base_url, &self.auth, &self.user_agent, &path);
664 self.run(coroutine)
665 }
666
667 pub fn enum_cards(
670 &mut self,
671 addressbook_id: &str,
672 ) -> Result<BTreeSet<CardRef>, WebdavClientStdError> {
673 let path = addressbook_path(self.addressbook_home_set.as_ref(), addressbook_id)?;
674 let coroutine = EnumCards::new(&self.base_url, &self.auth, &self.user_agent, &path);
675 self.run(coroutine)
676 }
677
678 pub fn multiget_cards(
681 &mut self,
682 addressbook_id: &str,
683 ids: &[&str],
684 ) -> Result<Vec<CardEntry>, WebdavClientStdError> {
685 let path = addressbook_path(self.addressbook_home_set.as_ref(), addressbook_id)?;
686 let coroutine =
687 MultigetCards::new(&self.base_url, &self.auth, &self.user_agent, &path, ids);
688 self.run(coroutine)
689 }
690
691 pub fn sync_cards(
695 &mut self,
696 addressbook_id: &str,
697 sync_token: Option<&str>,
698 ) -> Result<SyncDelta, WebdavClientStdError> {
699 let path = addressbook_path(self.addressbook_home_set.as_ref(), addressbook_id)?;
700 let coroutine = SyncCollection::new(
701 &self.base_url,
702 &self.auth,
703 &self.user_agent,
704 &path,
705 sync_token,
706 &[GETETAG],
707 );
708 self.run(coroutine)
709 }
710
711 pub fn read_card(
713 &mut self,
714 addressbook_id: &str,
715 card_id: &str,
716 ) -> Result<CardBody, WebdavClientStdError> {
717 let path = addressbook_path(self.addressbook_home_set.as_ref(), addressbook_id)?;
718 let coroutine = ReadCard::new(&self.base_url, &self.auth, &self.user_agent, &path, card_id);
719 self.run(coroutine)
720 }
721
722 pub fn create_card(
724 &mut self,
725 addressbook_id: &str,
726 id: &str,
727 vcard: Vec<u8>,
728 ) -> Result<CreateCardOk, WebdavClientStdError> {
729 let path = addressbook_path(self.addressbook_home_set.as_ref(), addressbook_id)?;
730 let coroutine = CreateCard::new(
731 &self.base_url,
732 &self.auth,
733 &self.user_agent,
734 &path,
735 id,
736 vcard,
737 );
738 self.run(coroutine)
739 }
740
741 pub fn update_card(
743 &mut self,
744 addressbook_id: &str,
745 id: &str,
746 vcard: Vec<u8>,
747 if_match: Option<&str>,
748 ) -> Result<UpdateCardOk, WebdavClientStdError> {
749 let path = addressbook_path(self.addressbook_home_set.as_ref(), addressbook_id)?;
750 let coroutine = UpdateCard::new(
751 &self.base_url,
752 &self.auth,
753 &self.user_agent,
754 &path,
755 id,
756 vcard,
757 if_match,
758 );
759 self.run(coroutine)
760 }
761
762 pub fn delete_card(
764 &mut self,
765 addressbook_id: &str,
766 card_id: &str,
767 if_match: Option<&str>,
768 ) -> Result<(), WebdavClientStdError> {
769 let path = addressbook_path(self.addressbook_home_set.as_ref(), addressbook_id)?;
770 let coroutine = DeleteCard::new(
771 &self.base_url,
772 &self.auth,
773 &self.user_agent,
774 &path,
775 card_id,
776 if_match,
777 );
778 self.run(coroutine).map(|_| ())
779 }
780}
781
782fn pump(
785 stream: &mut dyn WebdavStream,
786 buf: &mut [u8],
787 yielded: WebdavYield,
788) -> Result<Option<usize>, io::Error> {
789 match yielded {
790 WebdavYield::WantsRead => Ok(Some(stream.read(buf)?)),
791 WebdavYield::WantsWrite(bytes) => {
792 stream.write_all(&bytes)?;
793 Ok(None)
794 }
795 }
796}
797
798fn calendar_path(home: Option<&Url>, calendar_id: &str) -> Result<String, WebdavClientStdError> {
799 let home = home.ok_or(WebdavClientStdError::MissingCalendarHomeSet)?;
800 let base = home.path().trim_end_matches('/');
801 let id = calendar_id.trim_matches('/');
802 Ok(format!("{base}/{id}"))
803}
804
805fn addressbook_path(
806 home: Option<&Url>,
807 addressbook_id: &str,
808) -> Result<String, WebdavClientStdError> {
809 let home = home.ok_or(WebdavClientStdError::MissingAddressbookHomeSet)?;
810 let base = home.path().trim_end_matches('/');
811 let id = addressbook_id.trim_matches('/');
812 Ok(format!("{base}/{id}"))
813}
814
815pub trait WebdavStream: Read + Write + Send {}
818impl<T: Read + Write + Send + ?Sized> WebdavStream for T {}