A webmail client. Forked from https://git.sr.ht/~migadu/alps
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

163 lines
4.1 KiB

  1. package alpscarddav
  2. import (
  3. "fmt"
  4. "net/http"
  5. "net/url"
  6. "git.sr.ht/~migadu/alps"
  7. alpsbase "git.sr.ht/~migadu/alps/plugins/base"
  8. "github.com/emersion/go-vcard"
  9. "github.com/emersion/go-webdav/carddav"
  10. )
  11. func sanityCheckURL(u *url.URL) error {
  12. req, err := http.NewRequest(http.MethodOptions, u.String(), nil)
  13. if err != nil {
  14. return err
  15. }
  16. resp, err := http.DefaultClient.Do(req)
  17. if err != nil {
  18. return err
  19. }
  20. resp.Body.Close()
  21. // Servers might require authentication to perform an OPTIONS request
  22. if resp.StatusCode/100 != 2 && resp.StatusCode != http.StatusUnauthorized {
  23. return fmt.Errorf("HTTP request failed: %v %v", resp.StatusCode, resp.Status)
  24. }
  25. return nil
  26. }
  27. type plugin struct {
  28. alps.GoPlugin
  29. url *url.URL
  30. homeSetCache map[string]string
  31. }
  32. func (p *plugin) client(session *alps.Session) (*carddav.Client, error) {
  33. return newClient(p.url, session)
  34. }
  35. func (p *plugin) clientWithAddressBook(session *alps.Session) (*carddav.Client, *carddav.AddressBook, error) {
  36. c, err := newClient(p.url, session)
  37. if err != nil {
  38. return nil, nil, fmt.Errorf("failed to create CardDAV client: %v", err)
  39. }
  40. homeSet, ok := p.homeSetCache[session.Username()]
  41. if !ok {
  42. principal, err := c.FindCurrentUserPrincipal()
  43. if err != nil {
  44. return nil, nil, fmt.Errorf("failed to query CardDAV principal: %v", err)
  45. }
  46. homeSet, err = c.FindAddressBookHomeSet(principal)
  47. if err != nil {
  48. return nil, nil, fmt.Errorf("failed to query CardDAV address book home set: %v", err)
  49. }
  50. p.homeSetCache[session.Username()] = homeSet
  51. // TODO: evict entries from the cache if it's getting too big
  52. }
  53. addressBooks, err := c.FindAddressBooks(homeSet)
  54. if err != nil {
  55. return nil, nil, fmt.Errorf("failed to query CardDAV address books: %v", err)
  56. }
  57. if len(addressBooks) == 0 {
  58. return nil, nil, errNoAddressBook
  59. }
  60. return c, &addressBooks[0], nil
  61. }
  62. func newPlugin(srv *alps.Server) (alps.Plugin, error) {
  63. u, err := srv.Upstream("carddavs", "carddav+insecure", "https", "http+insecure")
  64. if _, ok := err.(*alps.NoUpstreamError); ok {
  65. return nil, nil
  66. } else if err != nil {
  67. return nil, fmt.Errorf("carddav: failed to parse upstream CardDAV server: %v", err)
  68. }
  69. switch u.Scheme {
  70. case "carddavs":
  71. u.Scheme = "https"
  72. case "carddav+insecure", "http+insecure":
  73. u.Scheme = "http"
  74. }
  75. if u.Scheme == "" {
  76. s, err := carddav.Discover(u.Host)
  77. if err != nil {
  78. srv.Logger().Printf("carddav: failed to discover CardDAV server: %v", err)
  79. return nil, nil
  80. }
  81. u, err = url.Parse(s)
  82. if err != nil {
  83. return nil, fmt.Errorf("carddav: Discover returned an invalid URL: %v", err)
  84. }
  85. }
  86. if err := sanityCheckURL(u); err != nil {
  87. return nil, fmt.Errorf("carddav: failed to connect to CardDAV server %q: %v", u, err)
  88. }
  89. srv.Logger().Printf("Configured upstream CardDAV server: %v", u)
  90. p := &plugin{
  91. GoPlugin: alps.GoPlugin{Name: "carddav"},
  92. url: u,
  93. homeSetCache: make(map[string]string),
  94. }
  95. registerRoutes(p)
  96. p.Inject("compose.html", func(ctx *alps.Context, _data alps.RenderData) error {
  97. data := _data.(*alpsbase.ComposeRenderData)
  98. c, addressBook, err := p.clientWithAddressBook(ctx.Session)
  99. if err == errNoAddressBook {
  100. return nil
  101. } else if err != nil {
  102. return err
  103. }
  104. query := carddav.AddressBookQuery{
  105. DataRequest: carddav.AddressDataRequest{
  106. Props: []string{vcard.FieldFormattedName, vcard.FieldEmail},
  107. },
  108. PropFilters: []carddav.PropFilter{{
  109. Name: vcard.FieldEmail,
  110. }},
  111. }
  112. addrs, err := c.QueryAddressBook(addressBook.Path, &query)
  113. if err != nil {
  114. return fmt.Errorf("failed to query CardDAV addresses: %v", err)
  115. }
  116. // TODO: cache the results
  117. emails := make([]string, 0, len(addrs))
  118. for _, addr := range addrs {
  119. cardEmails := addr.Card.Values(vcard.FieldEmail)
  120. emails = append(emails, cardEmails...)
  121. }
  122. data.Extra["EmailSuggestions"] = emails
  123. return nil
  124. })
  125. return p.Plugin(), nil
  126. }
  127. func init() {
  128. alps.RegisterPluginLoader(func(s *alps.Server) ([]alps.Plugin, error) {
  129. p, err := newPlugin(s)
  130. if err != nil {
  131. return nil, err
  132. }
  133. if p == nil {
  134. return nil, nil
  135. }
  136. return []alps.Plugin{p}, err
  137. })
  138. }