Modify the authentication handler to return an unauthorized error when an invalid or disabled bearer token is provided, rather than silently falling back to an anonymous request. This ensures that clients attempting to authenticate but failing (due to expired, malformed, or disabled tokens) are explicitly notified of the auth failure instead of proceeding anonymously. True anonymous requests without any Authorization header remain supported.
194 lines
5.4 KiB
Go
194 lines
5.4 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"encoding/xml"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"path"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
type webDAVStorageBackend struct {
|
|
cfg StorageBackendConfig
|
|
client *http.Client
|
|
}
|
|
|
|
func (b webDAVStorageBackend) ID() string { return b.cfg.ID }
|
|
func (b webDAVStorageBackend) Type() string { return StorageBackendWebDAV }
|
|
|
|
func (b webDAVStorageBackend) Put(ctx context.Context, key string, body io.Reader, _ int64, contentType string) error {
|
|
if err := b.mkcolParents(ctx, key); err != nil {
|
|
return err
|
|
}
|
|
request, err := b.request(ctx, http.MethodPut, key, body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if contentType != "" {
|
|
request.Header.Set("Content-Type", contentType)
|
|
}
|
|
response, err := b.client.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer response.Body.Close()
|
|
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
|
return fmt.Errorf("webdav put failed: %s", response.Status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b webDAVStorageBackend) Get(ctx context.Context, key string) (StorageObject, error) {
|
|
request, err := b.request(ctx, http.MethodGet, key, nil)
|
|
if err != nil {
|
|
return StorageObject{}, err
|
|
}
|
|
response, err := b.client.Do(request)
|
|
if err != nil {
|
|
return StorageObject{}, err
|
|
}
|
|
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
|
response.Body.Close()
|
|
return StorageObject{}, fmt.Errorf("webdav get failed: %s", response.Status)
|
|
}
|
|
modTime, _ := time.Parse(http.TimeFormat, response.Header.Get("Last-Modified"))
|
|
return StorageObject{Key: key, Size: response.ContentLength, ContentType: response.Header.Get("Content-Type"), ModTime: modTime, Body: response.Body}, nil
|
|
}
|
|
|
|
func (b webDAVStorageBackend) Delete(ctx context.Context, key string) error {
|
|
return b.deletePath(ctx, key)
|
|
}
|
|
|
|
func (b webDAVStorageBackend) DeletePrefix(ctx context.Context, prefix string) error {
|
|
return b.deletePath(ctx, strings.TrimSuffix(prefix, "/")+"/")
|
|
}
|
|
|
|
func (b webDAVStorageBackend) Usage(ctx context.Context) (int64, error) {
|
|
request, err := b.request(ctx, "PROPFIND", "", nil)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
request.Header.Set("Depth", "infinity")
|
|
request.Header.Set("Content-Type", "application/xml")
|
|
response, err := b.client.Do(request)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer response.Body.Close()
|
|
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
|
return 0, fmt.Errorf("webdav usage failed: %s", response.Status)
|
|
}
|
|
var multi webDAVMultiStatus
|
|
if err := xml.NewDecoder(response.Body).Decode(&multi); err != nil {
|
|
return 0, err
|
|
}
|
|
var total int64
|
|
for _, item := range multi.Responses {
|
|
if item.PropStat.Prop.ResourceType.Collection != nil {
|
|
continue
|
|
}
|
|
total += item.PropStat.Prop.ContentLength
|
|
}
|
|
return total, nil
|
|
}
|
|
|
|
func (b webDAVStorageBackend) Test(ctx context.Context) error {
|
|
key := ".warpbox-storage-test-" + randomID(6)
|
|
if err := b.Put(ctx, key, strings.NewReader("ok"), 2, "text/plain"); err != nil {
|
|
return err
|
|
}
|
|
return b.Delete(ctx, key)
|
|
}
|
|
|
|
func (b webDAVStorageBackend) deletePath(ctx context.Context, key string) error {
|
|
request, err := b.request(ctx, http.MethodDelete, key, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
response, err := b.client.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer response.Body.Close()
|
|
if response.StatusCode == http.StatusNotFound {
|
|
return nil
|
|
}
|
|
if response.StatusCode < 200 || response.StatusCode >= 300 {
|
|
return fmt.Errorf("webdav delete failed: %s", response.Status)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b webDAVStorageBackend) mkcolParents(ctx context.Context, key string) error {
|
|
dir := path.Dir(cleanObjectKey(key))
|
|
if dir == "." || dir == "/" {
|
|
return nil
|
|
}
|
|
parts := strings.Split(strings.Trim(dir, "/"), "/")
|
|
current := ""
|
|
for _, part := range parts {
|
|
current = path.Join(current, part)
|
|
request, err := b.request(ctx, "MKCOL", strings.TrimSuffix(current, "/")+"/", nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
response, err := b.client.Do(request)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
response.Body.Close()
|
|
if response.StatusCode != http.StatusCreated && response.StatusCode != http.StatusMethodNotAllowed && response.StatusCode != http.StatusConflict {
|
|
return fmt.Errorf("webdav mkcol failed: %s", response.Status)
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (b webDAVStorageBackend) request(ctx context.Context, method, key string, body io.Reader) (*http.Request, error) {
|
|
endpoint := strings.TrimRight(b.cfg.Endpoint, "/")
|
|
if endpoint == "" {
|
|
return nil, fmt.Errorf("webdav url is required")
|
|
}
|
|
remote := path.Join(cleanRemoteRoot(b.cfg.RemotePath), cleanObjectKey(key))
|
|
if strings.HasSuffix(key, "/") && !strings.HasSuffix(remote, "/") {
|
|
remote += "/"
|
|
}
|
|
target := endpoint + "/" + strings.TrimLeft(remote, "/")
|
|
request, err := http.NewRequestWithContext(ctx, method, target, body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if b.cfg.Username != "" || b.cfg.Password != "" {
|
|
request.SetBasicAuth(b.cfg.Username, b.cfg.Password)
|
|
}
|
|
return request, nil
|
|
}
|
|
|
|
type webDAVMultiStatus struct {
|
|
Responses []webDAVResponse `xml:"response"`
|
|
}
|
|
|
|
type webDAVResponse struct {
|
|
PropStat webDAVPropStat `xml:"propstat"`
|
|
}
|
|
|
|
type webDAVPropStat struct {
|
|
Prop webDAVProp `xml:"prop"`
|
|
}
|
|
|
|
type webDAVProp struct {
|
|
ContentLength int64 `xml:"getcontentlength"`
|
|
ResourceType webDAVResourceType `xml:"resourcetype"`
|
|
}
|
|
|
|
type webDAVResourceType struct {
|
|
Collection *struct{} `xml:"collection"`
|
|
}
|
|
|
|
func newWebDAVStorageBackend(cfg StorageBackendConfig) webDAVStorageBackend {
|
|
return webDAVStorageBackend{cfg: cfg, client: http.DefaultClient}
|
|
}
|