diff --git a/client.go b/client.go index 0d31467..76d24dc 100644 --- a/client.go +++ b/client.go @@ -3,6 +3,7 @@ package gowebdav import ( "bytes" "encoding/xml" + "fmt" "io" "net/http" "net/url" @@ -346,6 +347,43 @@ func (c *Client) ReadStream(path string) (io.ReadCloser, error) { return nil, newPathError("ReadStream", path, rs.StatusCode) } +// ReadStreamRange reads the stream representing a subset of bytes for a given path, +// utilizing HTTP Range Requests if the server supports it. +// The range is expressed as offset from the start of the file and length, for example +// offset=10, length=10 will return bytes 10 through 19. +// +// If the server does not support partial content requests and returns full content instead, +// this function will emulate the behavior by skipping `offset` bytes and limiting the result +// to `length`. +func (c *Client) ReadStreamRange(path string, offset, length int64) (io.ReadCloser, error) { + rs, err := c.req("GET", path, nil, func(r *http.Request) { + r.Header.Add("Range", fmt.Sprintf("bytes=%v-%v", offset, offset+length-1)) + }) + if err != nil { + return nil, newPathErrorErr("ReadStreamRange", path, err) + } + + if rs.StatusCode == http.StatusPartialContent { + // server supported partial content, return as-is. + return rs.Body, nil + } + + // server returned success, but did not support partial content, so we have the whole + // stream in rs.Body + if rs.StatusCode == 200 { + // discard first 'offset' bytes. + if _, err := io.Copy(io.Discard, io.LimitReader(rs.Body, offset)); err != nil { + return nil, newPathErrorErr("ReadStreamRange", path, err) + } + + // return a io.ReadCloser that is limited to `length` bytes. + return &limitedReadCloser{rs.Body, int(length)}, nil + } + + rs.Body.Close() + return nil, newPathError("ReadStream", path, rs.StatusCode) +} + // Write writes data to a given path func (c *Client) Write(path string, data []byte, _ os.FileMode) error { s := c.put(path, bytes.NewReader(data)) diff --git a/utils.go b/utils.go index 5b96468..f82592a 100644 --- a/utils.go +++ b/utils.go @@ -108,3 +108,28 @@ func parseXML(data io.Reader, resp interface{}, parse func(resp interface{}) err } return nil } + +// limitedReadCloser wraps a io.ReadCloser and limits the number of bytes that can be read from it. +type limitedReadCloser struct { + rc io.ReadCloser + remaining int +} + +func (l *limitedReadCloser) Read(buf []byte) (int, error) { + if l.remaining <= 0 { + return 0, io.EOF + } + + if len(buf) > l.remaining { + buf = buf[0:l.remaining] + } + + n, err := l.rc.Read(buf) + l.remaining -= n + + return n, err +} + +func (l *limitedReadCloser) Close() error { + return l.rc.Close() +}