mirror of
https://github.com/1f349/go-webdav.git
synced 2024-12-22 08:14:15 +00:00
webdav: add support for ETag to client & server
This commit is contained in:
parent
3268102d5a
commit
f04c1c9421
27
client.go
27
client.go
@ -4,6 +4,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/emersion/go-webdav/internal"
|
"github.com/emersion/go-webdav/internal"
|
||||||
@ -45,6 +46,14 @@ func (c *Client) FindCurrentUserPrincipal() (string, error) {
|
|||||||
return prop.Href.Path, nil
|
return prop.Href.Path, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var fileInfoPropfind = internal.NewPropNamePropfind(
|
||||||
|
internal.ResourceTypeName,
|
||||||
|
internal.GetContentLengthName,
|
||||||
|
internal.GetLastModifiedName,
|
||||||
|
internal.GetContentTypeName,
|
||||||
|
internal.GetETagName,
|
||||||
|
)
|
||||||
|
|
||||||
func fileInfoFromResponse(resp *internal.Response) (*FileInfo, error) {
|
func fileInfoFromResponse(resp *internal.Response) (*FileInfo, error) {
|
||||||
path, err := resp.Path()
|
path, err := resp.Path()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -75,22 +84,24 @@ func fileInfoFromResponse(resp *internal.Response) (*FileInfo, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var getETag internal.GetETag
|
||||||
|
if err := resp.DecodeProp(&getETag); err != nil && !internal.IsNotFound(err) {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
etag, err := strconv.Unquote(getETag.ETag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("webdav: failed to unquote ETag: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
fi.Size = getLen.Length
|
fi.Size = getLen.Length
|
||||||
fi.ModTime = time.Time(getMod.LastModified)
|
fi.ModTime = time.Time(getMod.LastModified)
|
||||||
fi.MIMEType = getType.Type
|
fi.MIMEType = getType.Type
|
||||||
|
fi.ETag = etag
|
||||||
}
|
}
|
||||||
|
|
||||||
return fi, nil
|
return fi, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: getetag
|
|
||||||
var fileInfoPropfind = internal.NewPropNamePropfind(
|
|
||||||
internal.ResourceTypeName,
|
|
||||||
internal.GetContentLengthName,
|
|
||||||
internal.GetLastModifiedName,
|
|
||||||
internal.GetContentTypeName,
|
|
||||||
)
|
|
||||||
|
|
||||||
func (c *Client) Stat(name string) (*FileInfo, error) {
|
func (c *Client) Stat(name string) (*FileInfo, error) {
|
||||||
resp, err := c.ic.PropfindFlat(name, fileInfoPropfind)
|
resp, err := c.ic.PropfindFlat(name, fileInfoPropfind)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package webdav
|
package webdav
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
@ -49,6 +50,12 @@ func fileInfoFromOS(p string, fi os.FileInfo) *FileInfo {
|
|||||||
IsDir: fi.IsDir(),
|
IsDir: fi.IsDir(),
|
||||||
// TODO: fallback to http.DetectContentType?
|
// TODO: fallback to http.DetectContentType?
|
||||||
MIMEType: mime.TypeByExtension(path.Ext(p)),
|
MIMEType: mime.TypeByExtension(path.Ext(p)),
|
||||||
|
// RFC 2616 section 13.3.3 describes strong ETags. Ideally these would
|
||||||
|
// be checksums or sequence numbers, however these are expensive to
|
||||||
|
// compute. The modification time with nanosecond granularity is good
|
||||||
|
// enough, as it's very unlikely for the same file to be modified twice
|
||||||
|
// during a single nanosecond.
|
||||||
|
ETag: fmt.Sprintf("%x%x", fi.ModTime().UnixNano(), fi.Size()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -339,6 +339,12 @@ type GetLastModified struct {
|
|||||||
LastModified Time `xml:",chardata"`
|
LastModified Time `xml:",chardata"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://tools.ietf.org/html/rfc4918#section-15.6
|
||||||
|
type GetETag struct {
|
||||||
|
XMLName xml.Name `xml:"DAV: getetag"`
|
||||||
|
ETag string `xml:",chardata"`
|
||||||
|
}
|
||||||
|
|
||||||
// https://tools.ietf.org/html/rfc4918#section-14.5
|
// https://tools.ietf.org/html/rfc4918#section-14.5
|
||||||
type Error struct {
|
type Error struct {
|
||||||
XMLName xml.Name `xml:"DAV: error"`
|
XMLName xml.Name `xml:"DAV: error"`
|
||||||
|
12
server.go
12
server.go
@ -2,6 +2,7 @@ package webdav
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
@ -87,13 +88,16 @@ func (b *backend) HeadGet(w http.ResponseWriter, r *http.Request) error {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
w.Header().Set("Content-Length", strconv.FormatInt(fi.Size, 10))
|
||||||
if fi.MIMEType != "" {
|
if fi.MIMEType != "" {
|
||||||
w.Header().Set("Content-Type", fi.MIMEType)
|
w.Header().Set("Content-Type", fi.MIMEType)
|
||||||
}
|
}
|
||||||
if !fi.ModTime.IsZero() {
|
if !fi.ModTime.IsZero() {
|
||||||
w.Header().Set("Last-Modified", fi.ModTime.UTC().Format(http.TimeFormat))
|
w.Header().Set("Last-Modified", fi.ModTime.UTC().Format(http.TimeFormat))
|
||||||
}
|
}
|
||||||
w.Header().Set("Content-Length", strconv.FormatInt(fi.Size, 10))
|
if fi.ETag != "" {
|
||||||
|
w.Header().Set("ETag", fmt.Sprintf("%q", fi.ETag))
|
||||||
|
}
|
||||||
|
|
||||||
if rs, ok := f.(io.ReadSeeker); ok {
|
if rs, ok := f.(io.ReadSeeker); ok {
|
||||||
// If it's an io.Seeker, use http.ServeContent which supports ranges
|
// If it's an io.Seeker, use http.ServeContent which supports ranges
|
||||||
@ -171,7 +175,11 @@ func (b *backend) propfindFile(propfind *internal.Propfind, fi *FileInfo) (*inte
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: getetag
|
if fi.ETag != "" {
|
||||||
|
props[internal.GetETagName] = func(*internal.RawXMLValue) (interface{}, error) {
|
||||||
|
return &internal.GetETag{ETag: fmt.Sprintf("%q", fi.ETag)}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return internal.NewPropfindResponse(fi.Path, propfind, props)
|
return internal.NewPropfindResponse(fi.Path, propfind, props)
|
||||||
|
Loading…
Reference in New Issue
Block a user