WebDAV: Refactor service discovery heuristic to add custom headers #4608

Signed-off-by: Michael Mayer <michael@photoprism.app>
This commit is contained in:
Michael Mayer
2025-03-27 03:31:48 +01:00
parent a36e8b19f8
commit 8836f90251
5 changed files with 86 additions and 62 deletions

View File

@@ -67,7 +67,7 @@ func Discover(rawUrl, user, pass string) (result Account, err error) {
result.AccName = serviceUrl.Host
}
result.AccType = h.ServiceType
result.AccType = h.Type
result.AccURL = serviceUrl.String()
return result, nil

View File

@@ -5,24 +5,34 @@ import (
"strings"
)
type Headers = map[string]string
// Heuristic represents a heuristic for detecting a remote service type, e.g. WebDAV.
type Heuristic struct {
ServiceType string
Type Type
Domains []string
Paths []string
Method string
Headers Headers
}
// Heuristics for common remote service types.
var Heuristics = []Heuristic{
{Facebook, []string{"facebook.com", "www.facebook.com"}, []string{}, "GET"},
{Twitter, []string{"twitter.com"}, []string{}, "GET"},
{Flickr, []string{"flickr.com", "www.flickr.com"}, []string{}, "GET"},
{Instagram, []string{"instagram.com", "www.instagram.com"}, []string{}, "GET"},
{Telegram, []string{"web.telegram.org", "www.telegram.org", "telegram.org"}, []string{}, "GET"},
{WhatsApp, []string{"web.whatsapp.com", "www.whatsapp.com", "whatsapp.com"}, []string{}, "GET"},
{OneDrive, []string{"onedrive.live.com"}, []string{}, "GET"},
{GDrive, []string{"drive.google.com"}, []string{}, "GET"},
{GPhotos, []string{"photos.google.com"}, []string{}, "GET"},
{WebDAV, []string{}, []string{"/", "/webdav/", "/originals/", "/remote.php/dav/files/{user}/", "/remote.php/webdav/", "/dav/files/{user}/", "/servlet/webdav.infostore/"}, "PROPFIND"},
{Type: Facebook, Domains: []string{"facebook.com", "www.facebook.com"}, Paths: []string{}, Method: "GET"},
{Type: Twitter, Domains: []string{"twitter.com"}, Paths: []string{}, Method: "GET"},
{Type: Flickr, Domains: []string{"flickr.com", "www.flickr.com"}, Paths: []string{}, Method: "GET"},
{Type: Instagram, Domains: []string{"instagram.com", "www.instagram.com"}, Paths: []string{}, Method: "GET"},
{Type: Telegram, Domains: []string{"web.telegram.org", "www.telegram.org", "telegram.org"}, Paths: []string{}, Method: "GET"},
{Type: WhatsApp, Domains: []string{"web.whatsapp.com", "www.whatsapp.com", "whatsapp.com"}, Paths: []string{}, Method: "GET"},
{Type: OneDrive, Domains: []string{"onedrive.live.com"}, Paths: []string{}, Method: "GET"},
{Type: GDrive, Domains: []string{"drive.google.com"}, Paths: []string{}, Method: "GET"},
{Type: GPhotos, Domains: []string{"photos.google.com"}, Paths: []string{}, Method: "GET"},
{Type: WebDAV,
Domains: []string{},
Paths: []string{"/", "/webdav/", "/originals/", "/remote.php/dav/files/{user}/", "/remote.php/webdav/", "/dav/files/{user}/", "/servlet/webdav.infostore/"},
Method: "PROPFIND",
Headers: Headers{"Depth": "1"},
},
}
func (h Heuristic) MatchDomain(match string) bool {
@@ -46,14 +56,14 @@ func (h Heuristic) Discover(rawUrl, user string) *url.URL {
return nil
}
if HttpOk(h.Method, u.String()) {
if h.TestRequest(h.Method, u.String()) {
return u
}
for _, p := range h.Paths {
u.Path = strings.Replace(p, "{user}", user, -1)
if HttpOk(h.Method, u.String()) {
if h.TestRequest(h.Method, u.String()) {
return u
}
}

View File

@@ -0,0 +1,41 @@
package service
import (
"net/http"
"time"
)
// TestRequest makes a test request to the given URL and returns true if successful.
func (h Heuristic) TestRequest(method, rawUrl string) bool {
req, err := http.NewRequest(method, rawUrl, nil)
if err != nil {
return false
}
// Add custom request headers:
// https://github.com/photoprism/photoprism/pull/4608
if len(h.Headers) > 0 {
for key, val := range h.Headers {
req.Header.Add(key, val)
}
}
// Create new http.Client instance.
//
// NOTE: Timeout specifies a time limit for requests made by
// this Client. The timeout includes connection time, any
// redirects, and reading the response body. The timer remains
// running after Get, Head, Post, or Do return and will
// interrupt reading of the Response.Body.
client := &http.Client{Timeout: 30 * time.Second}
// Send request to see if it fails.
if resp, reqErr := client.Do(req); reqErr != nil {
return false
} else if resp.StatusCode < 400 {
return true
}
return false
}

View File

@@ -23,48 +23,3 @@ Additional information can be found in our Developer Guide:
<https://docs.photoprism.app/developer-guide/>
*/
package service
import (
"net/http"
"time"
)
const (
WebDAV = "webdav"
Facebook = "facebook"
Twitter = "twitter"
Flickr = "flickr"
Instagram = "instagram"
Telegram = "telegram"
WhatsApp = "whatsapp"
GPhotos = "gphotos"
GDrive = "gdrive"
OneDrive = "onedrive"
)
func HttpOk(method, rawUrl string) bool {
req, err := http.NewRequest(method, rawUrl, nil)
req.Header.Add("Depth", "1")
if err != nil {
return false
}
// Create new http.Client instance.
//
// NOTE: Timeout specifies a time limit for requests made by
// this Client. The timeout includes connection time, any
// redirects, and reading the response body. The timer remains
// running after Get, Head, Post, or Do return and will
// interrupt reading of the Response.Body.
client := &http.Client{Timeout: 30 * time.Second}
// Send request to see if it fails.
if resp, err := client.Do(req); err != nil {
return false
} else if resp.StatusCode < 400 {
return true
}
return false
}

18
internal/service/types.go Normal file
View File

@@ -0,0 +1,18 @@
package service
// Type represents a remote service type, e.g. WebDAV.
type Type = string
// Identifiers for common remote services.
const (
WebDAV Type = "webdav"
Facebook Type = "facebook"
Twitter Type = "twitter"
Flickr Type = "flickr"
Instagram Type = "instagram"
Telegram Type = "telegram"
WhatsApp Type = "whatsapp"
GPhotos Type = "gphotos"
GDrive Type = "gdrive"
OneDrive Type = "onedrive"
)