diff --git a/web/data/references_test.go b/web/data/references_test.go index 9b46d02773a..285a02ba2c1 100644 --- a/web/data/references_test.go +++ b/web/data/references_test.go @@ -44,7 +44,7 @@ func TestReferences(t *testing.T) { }, }) - t.Run("ListNotSynchronizedOn", func(t *testing.T) { + t.Run("ListReferencesHandler", func(t *testing.T) { e := testutils.CreateTestClient(t, ts.URL) // Make doc @@ -230,6 +230,41 @@ func TestReferences(t *testing.T) { obj.Value("data").Array().Length().Equal(1) obj.Path("$.data[0].id").Equal(fdoc.ID()) + // Add dummy references on io.cozy.apps%2ffoobaz and io.cozy.apps%2Ffooqux + foobazRef := couchdb.DocReference{ + ID: "io.cozy.apps%2ffoobaz", + Type: Type, + } + fooquxRef := couchdb.DocReference{ + ID: "io.cozy.apps%2Ffooqux", + Type: Type, + } + fdoc.ReferencedBy = append(fdoc.ReferencedBy, foobazRef, fooquxRef) + err = couchdb.UpdateDoc(testInstance, fdoc) + assert.NoError(t, err) + + // Check that we can find the reference with %2f + obj = e.GET("/data/"+Type+"/io.cozy.apps%2ffoobar/relationships/references"). + WithHeader("Authorization", "Bearer "+token). + Expect().Status(200). + JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). + Object() + + obj.Path("$.meta.count").Equal(1) + obj.Value("data").Array().Length().Equal(1) + obj.Path("$.data[0].id").Equal(fdoc.ID()) + + // Check that we can find the reference with %2F + obj = e.GET("/data/"+Type+"/io.cozy.apps%2Ffoobar/relationships/references"). + WithHeader("Authorization", "Bearer "+token). + Expect().Status(200). + JSON(httpexpect.ContentOpts{MediaType: "application/vnd.api+json"}). + Object() + + obj.Path("$.meta.count").Equal(1) + obj.Value("data").Array().Length().Equal(1) + obj.Path("$.data[0].id").Equal(fdoc.ID()) + // Remove the reference with a / e.DELETE("/data/"+Type+"/io.cozy.apps%2Ffoobar/relationships/references"). WithHeader("Authorization", "Bearer "+token). @@ -241,6 +276,28 @@ func TestReferences(t *testing.T) { }`)). Expect().Status(204) + // Remove the reference with a %2f + e.DELETE("/data/"+Type+"/io.cozy.apps%2ffoobaz/relationships/references"). + WithHeader("Authorization", "Bearer "+token). + WithHeader("Content-Type", "application/vnd.api+json"). + WithBytes([]byte(`{ + "data": [ + {"id": "` + fdoc.ID() + `", "type": "` + consts.Files + `"} + ] + }`)). + Expect().Status(204) + + // Remove the reference with a %2F + e.DELETE("/data/"+Type+"/io.cozy.apps%2Ffooqux/relationships/references"). + WithHeader("Authorization", "Bearer "+token). + WithHeader("Content-Type", "application/vnd.api+json"). + WithBytes([]byte(`{ + "data": [ + {"id": "` + fdoc.ID() + `", "type": "` + consts.Files + `"} + ] + }`)). + Expect().Status(204) + // Check that all the references have been removed fdoc2, err := testInstance.VFS().FileByID(fdoc.ID()) assert.NoError(t, err) diff --git a/web/files/references.go b/web/files/references.go index 1801aa84760..922bd32eb9f 100644 --- a/web/files/references.go +++ b/web/files/references.go @@ -77,6 +77,17 @@ func ListReferencesHandler(c echo.Context) error { if len(resCount.Rows) > 0 { count = int(resCount.Rows[0].Value.(float64)) } + + // XXX Some references can contain `%2f` instead of `/` in the id (legacy), + // and to preserve compatibility, we try to find those documents if no + // documents with the correct reference are found. + if count == 0 && strings.Contains(id, "/") { + key[1] = c.Param("docid") + err = couchdb.ExecView(instance, couchdb.FilesReferencedByView, reqCount, &resCount) + if err == nil && len(resCount.Rows) > 0 { + count = int(resCount.Rows[0].Value.(float64)) + } + } meta := &jsonapi.Meta{Count: &count} sort := c.QueryParam("sort") @@ -242,6 +253,17 @@ func RemoveReferencesHandler(c echo.Context) error { ID: id, } + // XXX References with an ID that contains a / could have it encoded as %2F + // (legacy). We delete the references for both versions to preserve + // compatibility. + var altRef *couchdb.DocReference + if strings.Contains(id, "/") { + altRef = &couchdb.DocReference{ + Type: doctype, + ID: c.Param("docid"), + } + } + if err := middlewares.AllowTypeAndID(c, permission.DELETE, doctype, id); err != nil { if middlewares.AllowWholeType(c, permission.PATCH, consts.Files) != nil { return err @@ -259,12 +281,18 @@ func RemoveReferencesHandler(c echo.Context) error { if dir != nil { oldDir := dir.Clone() dir.RemoveReferencedBy(docRef) + if altRef != nil { + dir.RemoveReferencedBy(*altRef) + } updateDirCozyMetadata(c, dir) docs[i] = dir oldDocs[i] = oldDir } else { oldFile := file.Clone().(*vfs.FileDoc) file.RemoveReferencedBy(docRef) + if altRef != nil { + file.RemoveReferencedBy(*altRef) + } updateFileCozyMetadata(c, file, false) _, _ = file.Path(fs) // Ensure the fullpath is filled to realtime _, _ = oldFile.Path(fs) // Ensure the fullpath is filled to realtime