Skip to content

Commit

Permalink
Fix uploading files to NextCloud via WebDAV (#4420)
Browse files Browse the repository at this point in the history
The HTTP request to NextCloud was made with Transfer-Encoding: chunked,
because of the way the Content-Length was set. But it looks like
NextCloud doesn't support that, and files were created empty. We can
avoid that by setting request.ContentLength.
  • Loading branch information
nono authored Jun 17, 2024
2 parents 0845562 + c6d7c4f commit f0ae74a
Show file tree
Hide file tree
Showing 4 changed files with 69 additions and 26 deletions.
15 changes: 11 additions & 4 deletions docs/nextcloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,8 @@ HTTP/1.1 204 No Content

This route can be used to create a copy of a file in the same directory, with a
copy suffix in its name. The new name can be optionaly given with the `Name`
parameter in the query-string.
parameter in the query-string, or the full path can be given with `Path`
parameter.

The `:account` parameter is the identifier of the NextCloud `io.cozy.account`.

Expand All @@ -275,7 +276,7 @@ The `*path` parameter is the path of the file on the NextCloud.
### Request

```http
POST /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/copy/Documents/wallpaper.jpg HTTP/1.1
POST /remote/nextcloud/4ab2155707bb6613a8b9463daf00381b/copy/Documents/wallpaper.jpg?Path=/Images/beach.jpg HTTP/1.1
Host: cozy.example.net
Authorization: Bearer eyJhbG...
```
Expand Down Expand Up @@ -303,7 +304,7 @@ Content-Type: application/json

## POST /remote/nextcloud/:account/downstream/*path

This route can be used to move a file from the NextCloud to the Cozy.
This route can be used to move/copy a file from the NextCloud to the Cozy.

The `:account` parameter is the identifier of the NextCloud `io.cozy.account`.

Expand All @@ -312,6 +313,9 @@ The `*path` parameter is the path of the file on the NextCloud.
The `To` parameter in the query-string must be given, as the ID of the
directory on the Cozy where the file will be put.

By default, the file will be moved, but using `Copy=true` in the query-string
will makes a copy.

**Note:** a permission on `POST io.cozy.files` is required to use this route.

### Request
Expand Down Expand Up @@ -392,7 +396,7 @@ Content-Type: application/vnd.api+json

## POST /remote/nextcloud/:account/upstream/*path

This route can be used to move a file from the Cozy to the NextCloud.
This route can be used to move/copy a file from the Cozy to the NextCloud.

The `:account` parameter is the identifier of the NextCloud `io.cozy.account`.

Expand All @@ -401,6 +405,9 @@ The `*path` parameter is the path of the file on the NextCloud.
The `From` parameter in the query-string must be given, as the ID of the
file on the Cozy that will be moved.

By default, the file will be moved, but using `Copy=true` in the query-string
will makes a copy.

**Note:** a permission on `POST io.cozy.files` is required to use this route.

### Request
Expand Down
28 changes: 19 additions & 9 deletions model/nextcloud/nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ import (
"github.com/labstack/echo/v4"
)

type OperationKind int

const (
MoveOperation OperationKind = iota
CopyOperation
)

type File struct {
DocID string `json:"id,omitempty"`
Type string `json:"type"`
Expand Down Expand Up @@ -109,11 +116,11 @@ func (nc *NextCloud) Download(path string) (*webdav.Download, error) {
return nc.webdav.Get(path)
}

func (nc *NextCloud) Upload(path, mime string, body io.Reader) error {
func (nc *NextCloud) Upload(path, mime string, contentLength int64, body io.Reader) error {
headers := map[string]string{
echo.HeaderContentType: mime,
}
return nc.webdav.Put(path, headers, body)
return nc.webdav.Put(path, contentLength, headers, body)
}

func (nc *NextCloud) Mkdir(path string) error {
Expand Down Expand Up @@ -160,7 +167,7 @@ func (nc *NextCloud) ListFiles(path string) ([]jsonapi.Object, error) {
return files, nil
}

func (nc *NextCloud) Downstream(path, dirID string, cozyMetadata *vfs.FilesCozyMetadata) (*vfs.FileDoc, error) {
func (nc *NextCloud) Downstream(path, dirID string, kind OperationKind, cozyMetadata *vfs.FilesCozyMetadata) (*vfs.FileDoc, error) {
dl, err := nc.webdav.Get(path)
if err != nil {
return nil, err
Expand Down Expand Up @@ -201,11 +208,13 @@ func (nc *NextCloud) Downstream(path, dirID string, cozyMetadata *vfs.FilesCozyM
return nil, err
}

_ = nc.webdav.Delete(path)
if kind == MoveOperation {
_ = nc.webdav.Delete(path)
}
return doc, nil
}

func (nc *NextCloud) Upstream(path, from string) error {
func (nc *NextCloud) Upstream(path, from string, kind OperationKind) error {
fs := nc.inst.VFS()
doc, err := fs.FileByID(from)
if err != nil {
Expand All @@ -218,13 +227,14 @@ func (nc *NextCloud) Upstream(path, from string) error {
defer f.Close()

headers := map[string]string{
echo.HeaderContentType: doc.Mime,
echo.HeaderContentLength: strconv.Itoa(int(doc.ByteSize)),
echo.HeaderContentType: doc.Mime,
}
if err := nc.webdav.Put(path, headers, f); err != nil {
if err := nc.webdav.Put(path, doc.ByteSize, headers, f); err != nil {
return err
}
_ = fs.DestroyFile(doc)
if kind == MoveOperation {
_ = fs.DestroyFile(doc)
}
return nil
}

Expand Down
31 changes: 22 additions & 9 deletions pkg/webdav/webdav.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ type Client struct {
}

func (c *Client) Mkcol(path string) error {
res, err := c.req("MKCOL", path, nil, nil)
res, err := c.req("MKCOL", path, 0, nil, nil)
if err != nil {
return err
}
Expand All @@ -47,7 +47,7 @@ func (c *Client) Mkcol(path string) error {
}

func (c *Client) Delete(path string) error {
res, err := c.req("DELETE", path, nil, nil)
res, err := c.req("DELETE", path, 0, nil, nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -75,7 +75,7 @@ func (c *Client) Move(oldPath, newPath string) error {
"Destination": u.String(),
"Overwrite": "F",
}
res, err := c.req("MOVE", oldPath, headers, nil)
res, err := c.req("MOVE", oldPath, 0, headers, nil)
if err != nil {
return err
}
Expand Down Expand Up @@ -105,7 +105,7 @@ func (c *Client) Copy(oldPath, newPath string) error {
"Destination": u.String(),
"Overwrite": "F",
}
res, err := c.req("COPY", oldPath, headers, nil)
res, err := c.req("COPY", oldPath, 0, headers, nil)
if err != nil {
return err
}
Expand All @@ -124,8 +124,13 @@ func (c *Client) Copy(oldPath, newPath string) error {
}
}

func (c *Client) Put(path string, headers map[string]string, body io.Reader) error {
res, err := c.req("PUT", path, headers, body)
func (c *Client) Put(
path string,
contentLength int64,
headers map[string]string,
body io.Reader,
) error {
res, err := c.req("PUT", path, contentLength, headers, body)
if err != nil {
return err
}
Expand All @@ -145,7 +150,7 @@ func (c *Client) Put(path string, headers map[string]string, body io.Reader) err
}

func (c *Client) Get(path string) (*Download, error) {
res, err := c.req("GET", path, nil, nil)
res, err := c.req("GET", path, 0, nil, nil)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -187,7 +192,7 @@ func (c *Client) List(path string) ([]Item, error) {
"Depth": "1",
}
payload := strings.NewReader(ListFilesPayload)
res, err := c.req("PROPFIND", path, headers, payload)
res, err := c.req("PROPFIND", path, 0, headers, payload)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -300,7 +305,12 @@ const ListFilesPayload = `<?xml version="1.0"?>
</d:propfind>
`

func (c *Client) req(method, path string, headers map[string]string, body io.Reader) (*http.Response, error) {
func (c *Client) req(
method, path string,
contentLength int64,
headers map[string]string,
body io.Reader,
) (*http.Response, error) {
path = c.BasePath + fixSlashes(path)
u := url.URL{
Scheme: c.Scheme,
Expand All @@ -316,6 +326,9 @@ func (c *Client) req(method, path string, headers map[string]string, body io.Rea
for k, v := range headers {
req.Header.Set(k, v)
}
if contentLength > 0 {
req.ContentLength = contentLength
}
start := time.Now()
res, err := safehttp.ClientWithKeepAlive.Do(req)
elapsed := time.Since(start)
Expand Down
21 changes: 17 additions & 4 deletions web/remote/nextcloud.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"net/http"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/cozy/cozy-stack/model/nextcloud"
Expand Down Expand Up @@ -103,7 +104,7 @@ func nextcloudPut(c echo.Context) error {
func nextcloudUpload(c echo.Context, nc *nextcloud.NextCloud, path string) error {
req := c.Request()
mime := req.Header.Get(echo.HeaderContentType)
if err := nc.Upload(path, mime, req.Body); err != nil {
if err := nc.Upload(path, mime, req.ContentLength, req.Body); err != nil {
return wrapNextcloudErrors(err)
}
return c.JSON(http.StatusCreated, echo.Map{"ok": true})
Expand Down Expand Up @@ -166,7 +167,9 @@ func nextcloudCopy(c echo.Context) error {

oldPath := c.Param("*")
newPath := oldPath
if newName := c.QueryParam("Name"); newName != "" {
if p := c.QueryParam("Path"); p != "" {
newPath = p
} else if newName := c.QueryParam("Name"); newName != "" {
newPath = filepath.Join(filepath.Dir(oldPath), newName)
} else {
ext := filepath.Ext(oldPath)
Expand Down Expand Up @@ -199,8 +202,13 @@ func nextcloudDownstream(c echo.Context) error {
return jsonapi.BadRequest(errors.New("missing To parameter"))
}

kind := nextcloud.MoveOperation
if isCopy, _ := strconv.ParseBool(c.QueryParam("Copy")); isCopy {
kind = nextcloud.CopyOperation
}

cozyMetadata, _ := files.CozyMetadataFromClaims(c, true)
f, err := nc.Downstream(path, to, cozyMetadata)
f, err := nc.Downstream(path, to, kind, cozyMetadata)
if err != nil {
return wrapNextcloudErrors(err)
}
Expand All @@ -226,7 +234,12 @@ func nextcloudUpstream(c echo.Context) error {
return jsonapi.BadRequest(errors.New("missing From parameter"))
}

if err := nc.Upstream(path, from); err != nil {
kind := nextcloud.MoveOperation
if isCopy, _ := strconv.ParseBool(c.QueryParam("Copy")); isCopy {
kind = nextcloud.CopyOperation
}

if err := nc.Upstream(path, from, kind); err != nil {
return wrapNextcloudErrors(err)
}
return c.NoContent(http.StatusNoContent)
Expand Down

0 comments on commit f0ae74a

Please sign in to comment.