github.com/cs3org/reva/v2@v2.27.7/internal/http/services/owncloud/ocdav/propfind/propfind_test.go (about) 1 // Copyright 2018-2021 CERN 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 // 15 // In applying this license, CERN does not waive the privileges and immunities 16 // granted to it by virtue of its status as an Intergovernmental Organization 17 // or submit itself to any jurisdiction. 18 19 package propfind_test 20 21 import ( 22 "context" 23 "encoding/xml" 24 "io" 25 "net/http" 26 "net/http/httptest" 27 "strings" 28 29 gateway "github.com/cs3org/go-cs3apis/cs3/gateway/v1beta1" 30 link "github.com/cs3org/go-cs3apis/cs3/sharing/link/v1beta1" 31 sprovider "github.com/cs3org/go-cs3apis/cs3/storage/provider/v1beta1" 32 typesv1beta1 "github.com/cs3org/go-cs3apis/cs3/types/v1beta1" 33 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/config" 34 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/net" 35 "github.com/cs3org/reva/v2/internal/http/services/owncloud/ocdav/propfind" 36 "github.com/cs3org/reva/v2/pkg/rgrpc/status" 37 "github.com/cs3org/reva/v2/pkg/rgrpc/todo/pool" 38 "github.com/cs3org/reva/v2/pkg/storagespace" 39 "github.com/cs3org/reva/v2/pkg/utils" 40 "github.com/cs3org/reva/v2/tests/cs3mocks/mocks" 41 "github.com/stretchr/testify/mock" 42 "google.golang.org/grpc" 43 44 . "github.com/onsi/ginkgo/v2" 45 . "github.com/onsi/gomega" 46 ) 47 48 type selector struct { 49 client gateway.GatewayAPIClient 50 } 51 52 func (s selector) Next(opts ...pool.Option) (gateway.GatewayAPIClient, error) { 53 return s.client, nil 54 } 55 56 var _ = Describe("PropfindWithDepthInfinity", func() { 57 var ( 58 handler *propfind.Handler 59 client *mocks.GatewayAPIClient 60 ctx context.Context 61 62 readResponse = func(r io.Reader) (*propfind.MultiStatusResponseUnmarshalXML, string, error) { 63 buf, err := io.ReadAll(r) 64 if err != nil { 65 return nil, "", err 66 } 67 res := &propfind.MultiStatusResponseUnmarshalXML{} 68 err = xml.Unmarshal(buf, res) 69 if err != nil { 70 return nil, "", err 71 } 72 73 return res, string(buf), nil 74 } 75 76 mockStat = func(ref *sprovider.Reference, info *sprovider.ResourceInfo) { 77 client.On("Stat", mock.Anything, mock.MatchedBy(func(req *sprovider.StatRequest) bool { 78 return utils.ResourceIDEqual(req.Ref.ResourceId, ref.ResourceId) && 79 (ref.Path == "" || req.Ref.Path == ref.Path) 80 })).Return(&sprovider.StatResponse{ 81 Status: status.NewOK(ctx), 82 Info: info, 83 }, nil) 84 } 85 mockListContainer = func(ref *sprovider.Reference, infos []*sprovider.ResourceInfo) { 86 client.On("ListContainer", mock.Anything, mock.MatchedBy(func(req *sprovider.ListContainerRequest) bool { 87 match := utils.ResourceIDEqual(req.Ref.ResourceId, ref.ResourceId) && 88 (ref.Path == "" || req.Ref.Path == ref.Path) 89 return match 90 })).Return(&sprovider.ListContainerResponse{ 91 Status: status.NewOK(ctx), 92 Infos: infos, 93 }, nil) 94 } 95 96 foospace *sprovider.StorageSpace 97 fooquxspace *sprovider.StorageSpace 98 fooFileShareSpace *sprovider.StorageSpace 99 fooFileShare2Space *sprovider.StorageSpace 100 fooDirShareSpace *sprovider.StorageSpace 101 ) 102 103 JustBeforeEach(func() { 104 ctx = context.WithValue(context.Background(), net.CtxKeyBaseURI, "http://127.0.0.1:3000") 105 client = &mocks.GatewayAPIClient{} 106 sel := selector{ 107 client: client, 108 } 109 110 cfg := &config.Config{ 111 FilesNamespace: "/users/{{.Username}}", 112 WebdavNamespace: "/users/{{.Username}}", 113 AllowPropfindDepthInfinitiy: true, 114 NameValidation: config.NameValidation{ 115 MaxLength: 255, 116 InvalidChars: []string{"\f", "\r", "\n", "\\"}, 117 }, 118 } 119 120 handler = propfind.NewHandler("127.0.0.1:3000", sel, cfg) 121 122 foospace = &sprovider.StorageSpace{ 123 Opaque: &typesv1beta1.Opaque{ 124 Map: map[string]*typesv1beta1.OpaqueEntry{ 125 "path": { 126 Decoder: "plain", 127 Value: []byte("/foo"), 128 }, 129 }, 130 }, 131 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})}, 132 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, 133 Name: "foospace", 134 } 135 fooquxspace = &sprovider.StorageSpace{ 136 Opaque: &typesv1beta1.Opaque{ 137 Map: map[string]*typesv1beta1.OpaqueEntry{ 138 "path": { 139 Decoder: "plain", 140 Value: []byte("/foo/qux"), 141 }, 142 }, 143 }, 144 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"})}, 145 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, 146 Name: "fooquxspace", 147 } 148 fooFileShareSpace = &sprovider.StorageSpace{ 149 Opaque: &typesv1beta1.Opaque{ 150 Map: map[string]*typesv1beta1.OpaqueEntry{ 151 "path": { 152 Decoder: "plain", 153 Value: []byte("/foo/Shares/sharedFile"), 154 }, 155 }, 156 }, 157 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"})}, 158 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"}, 159 Name: "fooFileShareSpace", 160 } 161 fooFileShare2Space = &sprovider.StorageSpace{ 162 Opaque: &typesv1beta1.Opaque{ 163 Map: map[string]*typesv1beta1.OpaqueEntry{ 164 "path": { 165 Decoder: "plain", 166 Value: []byte("/foo/Shares/sharedFile2"), 167 }, 168 }, 169 }, 170 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"})}, 171 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"}, 172 Name: "fooFileShareSpace2", 173 } 174 fooDirShareSpace = &sprovider.StorageSpace{ 175 Opaque: &typesv1beta1.Opaque{ 176 Map: map[string]*typesv1beta1.OpaqueEntry{ 177 "path": { 178 Decoder: "plain", 179 Value: []byte("/foo/Shares/sharedDir"), 180 }, 181 }, 182 }, 183 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"})}, 184 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, 185 Name: "fooDirShareSpace", 186 } 187 188 // For the space mounted a /foo we assign a storageid "foospace" and a root opaqueid "root" 189 // it contains four resources 190 // - ./bar, file, 100 bytes, opaqueid "bar" 191 // - ./baz, file, 1 byte, opaqueid "baz" 192 // - ./dir, folder, 30 bytes, opaqueid "dir" 193 // - ./dir/entry, file, 30 bytes, opaqueid "direntry" 194 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "."}, 195 &sprovider.ResourceInfo{ 196 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, 197 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 198 Path: ".", 199 Size: uint64(131), 200 }) 201 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "."}, 202 []*sprovider.ResourceInfo{ 203 { 204 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 205 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, 206 Path: "bar", 207 Size: 100, 208 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 209 }, 210 { 211 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 212 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, 213 Path: "baz", 214 Size: 1, 215 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 216 }, 217 { 218 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 219 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, 220 Path: "dir", 221 Size: 30, 222 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 223 }, 224 }) 225 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./bar"}, 226 &sprovider.ResourceInfo{ 227 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, 228 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 229 Path: "./bar", 230 Size: uint64(100), 231 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 232 }) 233 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, Path: "."}, 234 &sprovider.ResourceInfo{ 235 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, 236 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 237 Path: "./bar", 238 Size: uint64(100), 239 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 240 }) 241 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./baz"}, 242 &sprovider.ResourceInfo{ 243 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, 244 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 245 Path: "./baz", 246 Size: uint64(1), 247 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 248 }) 249 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, Path: "."}, 250 &sprovider.ResourceInfo{ 251 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, 252 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 253 Path: "./baz", 254 Size: uint64(1), 255 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 256 }) 257 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir"}, 258 &sprovider.ResourceInfo{ 259 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, 260 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 261 Path: "./dir", 262 Size: uint64(30), 263 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 264 }) 265 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir&dir"}, 266 &sprovider.ResourceInfo{ 267 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, 268 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 269 Path: "./dir&dir", 270 Name: "dir&dir", 271 Size: uint64(30), 272 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 273 }) 274 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, Path: "."}, 275 &sprovider.ResourceInfo{ 276 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, 277 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 278 Path: "./dir", 279 Size: uint64(30), 280 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 281 }) 282 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir"}, 283 []*sprovider.ResourceInfo{ 284 { 285 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"}, 286 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 287 Path: "entry", 288 Size: 30, 289 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 290 }, 291 }) 292 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir&dir"}, 293 []*sprovider.ResourceInfo{ 294 { 295 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"}, 296 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 297 Path: "entry", 298 Size: 30, 299 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 300 }, 301 }) 302 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, Path: "."}, 303 []*sprovider.ResourceInfo{ 304 { 305 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"}, 306 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 307 Path: "entry", 308 Size: 30, 309 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 310 }, 311 }) 312 313 // For the space mounted a /foo/qux we assign a storageid "foospace" and a root opaqueid "root" 314 // it contains one resource 315 // - ./quux, file, 1000 bytes, opaqueid "quux" 316 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, Path: "."}, 317 &sprovider.ResourceInfo{ 318 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, 319 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 320 Path: ".", 321 Size: uint64(1000), 322 }) 323 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, Path: "."}, 324 []*sprovider.ResourceInfo{ 325 { 326 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "quux"}, 327 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 328 Path: "./quux", 329 Size: 1000, 330 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "fooquxspace"}, 331 }, 332 }) 333 334 // For the space mounted a /foo/Shares/sharedFile we assign a spaceid "fooFileShareSpace" and a root opaqueid "sharedfile" 335 // it is a file resource, 2000 bytes, opaqueid "sharedfile" 336 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"}, Path: "."}, 337 &sprovider.ResourceInfo{ 338 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"}, 339 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 340 Path: ".", 341 Size: uint64(2000), 342 Mtime: &typesv1beta1.Timestamp{Seconds: 1}, 343 Etag: "1", 344 }) 345 346 // For the space mounted a /foo/Shares/sharedFile2 we assign a spaceid "fooFileShareSpace2" and a root opaqueid "sharedfile2" 347 // it is a file resource, 2500 bytes, opaqueid "sharedfile2" 348 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"}, Path: "."}, 349 &sprovider.ResourceInfo{ 350 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"}, 351 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 352 Path: ".", 353 Size: uint64(2500), 354 Mtime: &typesv1beta1.Timestamp{Seconds: 2}, 355 Etag: "2", 356 }) 357 358 // For the space mounted a /foo/Shares/sharedFile2 we assign a spaceid "fooDirShareSpace" and a root opaqueid "shareddir" 359 // it is a folder containing one resource 360 // ./something, file, 1500 bytes, opaqueid "shareddirsomething" 361 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, Path: "."}, 362 &sprovider.ResourceInfo{ 363 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, 364 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 365 Path: ".", 366 Size: uint64(1500), 367 Mtime: &typesv1beta1.Timestamp{Seconds: 3}, 368 Etag: "3", 369 }) 370 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, Path: "."}, 371 []*sprovider.ResourceInfo{ 372 { 373 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddirsomething"}, 374 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 375 Path: "something", 376 Size: 1500, 377 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "fooDirShareSpace"}, 378 }, 379 }) 380 381 client.On("ListPublicShares", mock.Anything, mock.Anything).Return( 382 func(_ context.Context, req *link.ListPublicSharesRequest, _ ...grpc.CallOption) *link.ListPublicSharesResponse { 383 384 var shares []*link.PublicShare 385 if len(req.Filters) == 0 { 386 shares = []*link.PublicShare{} 387 } else { 388 term := req.Filters[0].Term.(*link.ListPublicSharesRequest_Filter_ResourceId) 389 switch { 390 case term != nil && term.ResourceId != nil && term.ResourceId.OpaqueId == "bar": 391 shares = []*link.PublicShare{ 392 { 393 Id: &link.PublicShareId{OpaqueId: "share1"}, 394 ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, 395 }, 396 } 397 default: 398 shares = []*link.PublicShare{} 399 } 400 } 401 return &link.ListPublicSharesResponse{ 402 Status: status.NewOK(ctx), 403 Share: shares, 404 } 405 }, nil) 406 }) 407 408 Describe("NewHandler", func() { 409 It("returns a handler", func() { 410 Expect(handler).ToNot(BeNil()) 411 }) 412 }) 413 414 Describe("HandlePathPropfind", func() { 415 Context("with just one space", func() { 416 JustBeforeEach(func() { 417 client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *sprovider.ListStorageSpacesRequest) bool { 418 p := string(req.Opaque.Map["path"].Value) 419 return p == "/" || strings.HasPrefix(p, "/foo") 420 })).Return(&sprovider.ListStorageSpacesResponse{ 421 Status: status.NewOK(ctx), 422 StorageSpaces: []*sprovider.StorageSpace{foospace}, 423 }, nil) 424 client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ 425 Status: status.NewOK(ctx), 426 StorageSpaces: []*sprovider.StorageSpace{}, 427 }, nil) 428 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, Path: "."}, 429 &sprovider.ResourceInfo{ 430 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 431 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 432 Path: ".", 433 Size: uint64(131), 434 }) 435 }) 436 437 It("verifies the depth header", func() { 438 rr := httptest.NewRecorder() 439 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 440 req.Header.Set(net.HeaderDepth, "invalid") 441 req = req.WithContext(ctx) 442 Expect(err).ToNot(HaveOccurred()) 443 444 handler.HandlePathPropfind(rr, req, "/") 445 Expect(rr.Code).To(Equal(http.StatusBadRequest)) 446 }) 447 448 It("stats a path", func() { 449 rr := httptest.NewRecorder() 450 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 451 req = req.WithContext(ctx) 452 Expect(err).ToNot(HaveOccurred()) 453 454 handler.HandlePathPropfind(rr, req, "/") 455 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 456 457 res, _, err := readResponse(rr.Result().Body) 458 Expect(err).ToNot(HaveOccurred()) 459 Expect(len(res.Responses)).To(Equal(4)) 460 461 root := res.Responses[0] 462 Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/")) 463 Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>131</oc:size>")) 464 465 bar := res.Responses[1] 466 Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar")) 467 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 468 469 baz := res.Responses[2] 470 Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz")) 471 Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>")) 472 473 dir := res.Responses[3] 474 Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/")) 475 Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>")) 476 }) 477 478 It("stats a file", func() { 479 rr := httptest.NewRecorder() 480 req, err := http.NewRequest("GET", "/foo/bar", strings.NewReader("")) 481 req = req.WithContext(ctx) 482 Expect(err).ToNot(HaveOccurred()) 483 484 handler.HandlePathPropfind(rr, req, "/") 485 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 486 487 res, _, err := readResponse(rr.Result().Body) 488 Expect(err).ToNot(HaveOccurred()) 489 Expect(len(res.Responses)).To(Equal(1)) 490 491 bar := res.Responses[0] 492 Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar")) 493 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 494 }) 495 }) 496 497 Context("with one nested file space", func() { 498 JustBeforeEach(func() { 499 client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( 500 func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { 501 var spaces []*sprovider.StorageSpace 502 switch string(req.Opaque.Map["path"].Value) { 503 case "/", "/foo": 504 spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace} 505 case "/foo/Shares", "/foo/Shares/sharedFile": 506 spaces = []*sprovider.StorageSpace{fooFileShareSpace} 507 } 508 return &sprovider.ListStorageSpacesResponse{ 509 Status: status.NewOK(ctx), 510 StorageSpaces: spaces, 511 } 512 }, 513 nil) 514 }) 515 516 It("stats the parent", func() { 517 rr := httptest.NewRecorder() 518 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 519 Expect(err).ToNot(HaveOccurred()) 520 req = req.WithContext(ctx) 521 522 handler.HandlePathPropfind(rr, req, "/") 523 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 524 525 res, _, err := readResponse(rr.Result().Body) 526 Expect(err).ToNot(HaveOccurred()) 527 Expect(len(res.Responses)).To(Equal(5)) 528 529 parent := res.Responses[0] 530 Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/")) 531 Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>2131</oc:size>")) 532 533 sf := res.Responses[4] 534 Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/")) 535 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>2000</oc:size>")) 536 }) 537 538 It("stats the embedded space", func() { 539 rr := httptest.NewRecorder() 540 req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader("")) 541 Expect(err).ToNot(HaveOccurred()) 542 req = req.WithContext(ctx) 543 544 handler.HandlePathPropfind(rr, req, "/") 545 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 546 547 res, _, err := readResponse(rr.Result().Body) 548 Expect(err).ToNot(HaveOccurred()) 549 Expect(len(res.Responses)).To(Equal(1)) 550 551 sf := res.Responses[0] 552 Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile")) 553 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>2000</d:getcontentlength>")) 554 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:01 GMT</d:getlastmodified>")) 555 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"1"</d:getetag>`)) 556 }) 557 }) 558 559 Context("with two nested file spaces and a nested directory space", func() { 560 JustBeforeEach(func() { 561 client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( 562 func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { 563 var spaces []*sprovider.StorageSpace 564 switch string(req.Opaque.Map["path"].Value) { 565 case "/", "/foo": 566 spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace, fooFileShare2Space, fooDirShareSpace} 567 case "/foo/Shares": 568 spaces = []*sprovider.StorageSpace{fooFileShareSpace, fooFileShare2Space, fooDirShareSpace} 569 case "/foo/Shares/sharedFile": 570 spaces = []*sprovider.StorageSpace{fooFileShareSpace} 571 case "/foo/Shares/sharedFile2": 572 spaces = []*sprovider.StorageSpace{fooFileShare2Space} 573 } 574 return &sprovider.ListStorageSpacesResponse{ 575 Status: status.NewOK(ctx), 576 StorageSpaces: spaces, 577 } 578 }, 579 nil) 580 }) 581 582 It("stats the parent", func() { 583 rr := httptest.NewRecorder() 584 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 585 Expect(err).ToNot(HaveOccurred()) 586 req = req.WithContext(ctx) 587 588 handler.HandlePathPropfind(rr, req, "/") 589 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 590 591 res, _, err := readResponse(rr.Result().Body) 592 Expect(err).ToNot(HaveOccurred()) 593 Expect(len(res.Responses)).To(Equal(5)) 594 595 parent := res.Responses[0] 596 Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/")) 597 Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>6131</oc:size>")) 598 599 shares := res.Responses[4] 600 Expect(shares.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/")) 601 Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>6000</oc:size>")) 602 Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:03 GMT</d:getlastmodified>")) 603 Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"3"</d:getetag>`)) 604 }) 605 606 It("stats the embedded space", func() { 607 rr := httptest.NewRecorder() 608 req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader("")) 609 Expect(err).ToNot(HaveOccurred()) 610 req = req.WithContext(ctx) 611 612 handler.HandlePathPropfind(rr, req, "/") 613 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 614 615 res, _, err := readResponse(rr.Result().Body) 616 Expect(err).ToNot(HaveOccurred()) 617 Expect(len(res.Responses)).To(Equal(1)) 618 619 sf := res.Responses[0] 620 Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile")) 621 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>2000</d:getcontentlength>")) 622 }) 623 624 It("includes all the things™ when depth is infinity", func() { 625 rr := httptest.NewRecorder() 626 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 627 Expect(err).ToNot(HaveOccurred()) 628 req = req.WithContext(ctx) 629 req.Header.Add(net.HeaderDepth, "infinity") 630 631 handler.HandlePathPropfind(rr, req, "/") 632 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 633 634 res, _, err := readResponse(rr.Result().Body) 635 Expect(err).ToNot(HaveOccurred()) 636 Expect(len(res.Responses)).To(Equal(9)) 637 638 paths := []string{} 639 for _, r := range res.Responses { 640 paths = append(paths, r.Href) 641 } 642 Expect(paths).To(ConsistOf( 643 "http:/127.0.0.1:3000/foo/", 644 "http:/127.0.0.1:3000/foo/bar", 645 "http:/127.0.0.1:3000/foo/baz", 646 "http:/127.0.0.1:3000/foo/dir/", 647 "http:/127.0.0.1:3000/foo/dir/entry", 648 "http:/127.0.0.1:3000/foo/Shares/sharedFile", 649 "http:/127.0.0.1:3000/foo/Shares/sharedFile2", 650 "http:/127.0.0.1:3000/foo/Shares/sharedDir/", 651 "http:/127.0.0.1:3000/foo/Shares/sharedDir/something", 652 )) 653 }) 654 }) 655 656 Context("with a nested directory space", func() { 657 JustBeforeEach(func() { 658 client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( 659 func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { 660 var spaces []*sprovider.StorageSpace 661 switch string(req.Opaque.Map["path"].Value) { 662 case "/", "/foo": 663 spaces = []*sprovider.StorageSpace{foospace, fooquxspace} 664 case "/foo/qux": 665 spaces = []*sprovider.StorageSpace{fooquxspace} 666 } 667 return &sprovider.ListStorageSpacesResponse{ 668 Status: status.NewOK(ctx), 669 StorageSpaces: spaces, 670 } 671 }, 672 nil) 673 }) 674 675 // Pending, the code for handling missing parents is still missing 676 PIt("handles children with no parent", func() { 677 rr := httptest.NewRecorder() 678 req, err := http.NewRequest("GET", "/", strings.NewReader("")) 679 Expect(err).ToNot(HaveOccurred()) 680 req = req.WithContext(ctx) 681 682 handler.HandlePathPropfind(rr, req, "/") 683 Expect(rr.Code).To(Equal(http.StatusOK)) 684 }) 685 686 It("mounts embedded spaces", func() { 687 rr := httptest.NewRecorder() 688 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 689 Expect(err).ToNot(HaveOccurred()) 690 req = req.WithContext(ctx) 691 692 handler.HandlePathPropfind(rr, req, "/") 693 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 694 695 res, _, err := readResponse(rr.Result().Body) 696 Expect(err).ToNot(HaveOccurred()) 697 Expect(len(res.Responses)).To(Equal(5)) 698 699 root := res.Responses[0] 700 Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/")) 701 Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1131</oc:size>")) 702 703 bar := res.Responses[1] 704 Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar")) 705 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 706 707 baz := res.Responses[2] 708 Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz")) 709 Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>")) 710 711 dir := res.Responses[3] 712 Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/")) 713 Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>")) 714 715 qux := res.Responses[4] 716 Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/")) 717 Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1000</oc:size>")) 718 }) 719 720 It("stats the embedded space", func() { 721 rr := httptest.NewRecorder() 722 req, err := http.NewRequest("GET", "/foo/qux/", strings.NewReader("")) 723 Expect(err).ToNot(HaveOccurred()) 724 req = req.WithContext(ctx) 725 726 handler.HandlePathPropfind(rr, req, "/") 727 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 728 729 res, _, err := readResponse(rr.Result().Body) 730 Expect(err).ToNot(HaveOccurred()) 731 Expect(len(res.Responses)).To(Equal(2)) 732 733 qux := res.Responses[0] 734 Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/")) 735 Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1000</oc:size>")) 736 737 quux := res.Responses[1] 738 Expect(quux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/quux")) 739 Expect(string(quux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1000</d:getcontentlength>")) 740 }) 741 }) 742 }) 743 744 Describe("HandleSpacesPropfind", func() { 745 /* 746 JustBeforeEach(func() { 747 client.On("Stat", mock.Anything, mock.Anything).Return(func(_ context.Context, req *sprovider.StatRequest, _ ...grpc.CallOption) *sprovider.StatResponse { 748 switch { 749 case req.Ref.ResourceId.OpaqueId == "foospace": 750 return &sprovider.StatResponse{ 751 Status: status.NewOK(ctx), 752 Info: &sprovider.ResourceInfo{ 753 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 754 Id: &sprovider.ResourceId{OpaqueId: "foospaceroot", StorageId: "foospaceroot"}, 755 Size: 131, 756 Path: ".", 757 }, 758 } 759 default: 760 return &sprovider.StatResponse{ 761 Status: status.NewNotFound(ctx, "not found"), 762 } 763 } 764 }, nil) 765 }) 766 */ 767 768 It("handles invalid space ids", func() { 769 client.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{ 770 Status: status.NewNotFound(ctx, "not found"), 771 }, nil) 772 773 rr := httptest.NewRecorder() 774 req, err := http.NewRequest("GET", "/", strings.NewReader("")) 775 Expect(err).ToNot(HaveOccurred()) 776 777 handler.HandleSpacesPropfind(rr, req, "does-not-exist") 778 Expect(rr.Code).To(Equal(http.StatusNotFound)) 779 }) 780 781 It("stats the space root", func() { 782 client.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{ 783 Status: status.NewNotFound(ctx, "not found"), 784 }, nil) 785 rr := httptest.NewRecorder() 786 req, err := http.NewRequest("GET", "/", strings.NewReader("")) 787 Expect(err).ToNot(HaveOccurred()) 788 req = req.WithContext(ctx) 789 790 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 791 spaceIDUrl := net.EncodePath(spaceID) 792 handler.HandleSpacesPropfind(rr, req, spaceID) 793 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 794 795 res, _, err := readResponse(rr.Result().Body) 796 Expect(err).ToNot(HaveOccurred()) 797 Expect(len(res.Responses)).To(Equal(4)) 798 799 root := res.Responses[0] 800 Expect(root.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/")) 801 Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>131</oc:size>")) 802 803 bar := res.Responses[1] 804 Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/bar")) 805 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 806 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>")) 807 808 baz := res.Responses[2] 809 Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/baz")) 810 Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>")) 811 Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>")) 812 813 dir := res.Responses[3] 814 Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/dir/")) 815 Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>")) 816 Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>")) 817 }) 818 819 It("stats a file", func() { 820 rr := httptest.NewRecorder() 821 req, err := http.NewRequest("GET", "/bar", strings.NewReader("")) 822 Expect(err).ToNot(HaveOccurred()) 823 req = req.WithContext(ctx) 824 825 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 826 handler.HandleSpacesPropfind(rr, req, spaceID) 827 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 828 829 res, _, err := readResponse(rr.Result().Body) 830 Expect(err).ToNot(HaveOccurred()) 831 Expect(len(res.Responses)).To(Equal(1)) 832 Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 833 }) 834 835 It("stats a directory", func() { 836 rr := httptest.NewRecorder() 837 req, err := http.NewRequest("GET", "/dir", strings.NewReader("")) 838 Expect(err).ToNot(HaveOccurred()) 839 req = req.WithContext(ctx) 840 841 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 842 handler.HandleSpacesPropfind(rr, req, spaceID) 843 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 844 845 res, _, err := readResponse(rr.Result().Body) 846 Expect(err).ToNot(HaveOccurred()) 847 Expect(len(res.Responses)).To(Equal(2)) 848 Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>")) 849 Expect(string(res.Responses[1].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>30</d:getcontentlength>")) 850 }) 851 852 It("stats a directory with xml special characters", func() { 853 rr := httptest.NewRecorder() 854 req, err := http.NewRequest("GET", "/dir&dir", strings.NewReader("")) 855 Expect(err).ToNot(HaveOccurred()) 856 req = req.WithContext(ctx) 857 858 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 859 handler.HandleSpacesPropfind(rr, req, spaceID) 860 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 861 862 res, _, err := readResponse(rr.Result().Body) 863 Expect(err).ToNot(HaveOccurred()) 864 Expect(len(res.Responses)).To(Equal(2)) 865 Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:name>dir&dir</oc:name>")) 866 Expect(string(res.Responses[1].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>30</d:getcontentlength>")) 867 }) 868 869 It("includes all the things™ when depth is infinity", func() { 870 rr := httptest.NewRecorder() 871 req, err := http.NewRequest("GET", "/", strings.NewReader("")) 872 req.Header.Add(net.HeaderDepth, "infinity") 873 Expect(err).ToNot(HaveOccurred()) 874 req = req.WithContext(ctx) 875 876 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 877 handler.HandleSpacesPropfind(rr, req, spaceID) 878 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 879 880 _, _, err = readResponse(rr.Result().Body) 881 Expect(err).ToNot(HaveOccurred()) 882 883 }) 884 }) 885 }) 886 887 var _ = Describe("PropfindWithoutDepthInfinity", func() { 888 var ( 889 handler *propfind.Handler 890 client *mocks.GatewayAPIClient 891 ctx context.Context 892 893 readResponse = func(r io.Reader) (*propfind.MultiStatusResponseUnmarshalXML, string, error) { 894 buf, err := io.ReadAll(r) 895 if err != nil { 896 return nil, "", err 897 } 898 res := &propfind.MultiStatusResponseUnmarshalXML{} 899 err = xml.Unmarshal(buf, res) 900 if err != nil { 901 return nil, "", err 902 } 903 904 return res, string(buf), nil 905 } 906 907 mockStat = func(ref *sprovider.Reference, info *sprovider.ResourceInfo) { 908 client.On("Stat", mock.Anything, mock.MatchedBy(func(req *sprovider.StatRequest) bool { 909 return utils.ResourceIDEqual(req.Ref.ResourceId, ref.ResourceId) && 910 (ref.Path == "" || req.Ref.Path == ref.Path) 911 })).Return(&sprovider.StatResponse{ 912 Status: status.NewOK(ctx), 913 Info: info, 914 }, nil) 915 } 916 mockListContainer = func(ref *sprovider.Reference, infos []*sprovider.ResourceInfo) { 917 client.On("ListContainer", mock.Anything, mock.MatchedBy(func(req *sprovider.ListContainerRequest) bool { 918 match := utils.ResourceIDEqual(req.Ref.ResourceId, ref.ResourceId) && 919 (ref.Path == "" || req.Ref.Path == ref.Path) 920 return match 921 })).Return(&sprovider.ListContainerResponse{ 922 Status: status.NewOK(ctx), 923 Infos: infos, 924 }, nil) 925 } 926 927 foospace *sprovider.StorageSpace 928 fooquxspace *sprovider.StorageSpace 929 fooFileShareSpace *sprovider.StorageSpace 930 fooFileShare2Space *sprovider.StorageSpace 931 fooDirShareSpace *sprovider.StorageSpace 932 ) 933 934 JustBeforeEach(func() { 935 ctx = context.WithValue(context.Background(), net.CtxKeyBaseURI, "http://127.0.0.1:3000") 936 client = &mocks.GatewayAPIClient{} 937 sel := selector{ 938 client: client, 939 } 940 941 cfg := &config.Config{ 942 FilesNamespace: "/users/{{.Username}}", 943 WebdavNamespace: "/users/{{.Username}}", 944 AllowPropfindDepthInfinitiy: false, 945 NameValidation: config.NameValidation{ 946 MaxLength: 255, 947 InvalidChars: []string{"\f", "\r", "\n", "\\"}, 948 }, 949 } 950 951 handler = propfind.NewHandler("127.0.0.1:3000", sel, cfg) 952 953 foospace = &sprovider.StorageSpace{ 954 Opaque: &typesv1beta1.Opaque{ 955 Map: map[string]*typesv1beta1.OpaqueEntry{ 956 "path": { 957 Decoder: "plain", 958 Value: []byte("/foo"), 959 }, 960 }, 961 }, 962 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"})}, 963 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, 964 Name: "foospace", 965 } 966 fooquxspace = &sprovider.StorageSpace{ 967 Opaque: &typesv1beta1.Opaque{ 968 Map: map[string]*typesv1beta1.OpaqueEntry{ 969 "path": { 970 Decoder: "plain", 971 Value: []byte("/foo/qux"), 972 }, 973 }, 974 }, 975 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"})}, 976 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, 977 Name: "fooquxspace", 978 } 979 fooFileShareSpace = &sprovider.StorageSpace{ 980 Opaque: &typesv1beta1.Opaque{ 981 Map: map[string]*typesv1beta1.OpaqueEntry{ 982 "path": { 983 Decoder: "plain", 984 Value: []byte("/foo/Shares/sharedFile"), 985 }, 986 }, 987 }, 988 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"})}, 989 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"}, 990 Name: "fooFileShareSpace", 991 } 992 fooFileShare2Space = &sprovider.StorageSpace{ 993 Opaque: &typesv1beta1.Opaque{ 994 Map: map[string]*typesv1beta1.OpaqueEntry{ 995 "path": { 996 Decoder: "plain", 997 Value: []byte("/foo/Shares/sharedFile2"), 998 }, 999 }, 1000 }, 1001 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"})}, 1002 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"}, 1003 Name: "fooFileShareSpace2", 1004 } 1005 fooDirShareSpace = &sprovider.StorageSpace{ 1006 Opaque: &typesv1beta1.Opaque{ 1007 Map: map[string]*typesv1beta1.OpaqueEntry{ 1008 "path": { 1009 Decoder: "plain", 1010 Value: []byte("/foo/Shares/sharedDir"), 1011 }, 1012 }, 1013 }, 1014 Id: &sprovider.StorageSpaceId{OpaqueId: storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"})}, 1015 Root: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, 1016 Name: "fooDirShareSpace", 1017 } 1018 1019 // For the space mounted a /foo we assign a storageid "foospace" and a root opaqueid "root" 1020 // it contains four resources 1021 // - ./bar, file, 100 bytes, opaqueid "bar" 1022 // - ./baz, file, 1 byte, opaqueid "baz" 1023 // - ./dir, folder, 30 bytes, opaqueid "dir" 1024 // - ./dir/entry, file, 30 bytes, opaqueid "direntry" 1025 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "."}, 1026 &sprovider.ResourceInfo{ 1027 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, 1028 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 1029 Path: ".", 1030 Size: uint64(131), 1031 }) 1032 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "."}, 1033 []*sprovider.ResourceInfo{ 1034 { 1035 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1036 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, 1037 Path: "bar", 1038 Size: 100, 1039 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1040 }, 1041 { 1042 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1043 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, 1044 Path: "baz", 1045 Size: 1, 1046 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1047 }, 1048 { 1049 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 1050 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, 1051 Path: "dir", 1052 Size: 30, 1053 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1054 }, 1055 }) 1056 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./bar"}, 1057 &sprovider.ResourceInfo{ 1058 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, 1059 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1060 Path: "./bar", 1061 Size: uint64(100), 1062 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1063 }) 1064 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, Path: "."}, 1065 &sprovider.ResourceInfo{ 1066 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, 1067 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1068 Path: "./bar", 1069 Size: uint64(100), 1070 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1071 }) 1072 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./baz"}, 1073 &sprovider.ResourceInfo{ 1074 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, 1075 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1076 Path: "./baz", 1077 Size: uint64(1), 1078 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1079 }) 1080 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, Path: "."}, 1081 &sprovider.ResourceInfo{ 1082 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "baz"}, 1083 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1084 Path: "./baz", 1085 Size: uint64(1), 1086 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1087 }) 1088 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir"}, 1089 &sprovider.ResourceInfo{ 1090 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, 1091 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 1092 Path: "./dir", 1093 Size: uint64(30), 1094 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1095 }) 1096 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir&dir"}, 1097 &sprovider.ResourceInfo{ 1098 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, 1099 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 1100 Path: "./dir&dir", 1101 Name: "dir&dir", 1102 Size: uint64(30), 1103 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1104 }) 1105 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, Path: "."}, 1106 &sprovider.ResourceInfo{ 1107 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, 1108 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 1109 Path: "./dir", 1110 Size: uint64(30), 1111 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1112 }) 1113 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir"}, 1114 []*sprovider.ResourceInfo{ 1115 { 1116 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"}, 1117 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1118 Path: "entry", 1119 Size: 30, 1120 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1121 }, 1122 }) 1123 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}, Path: "./dir&dir"}, 1124 []*sprovider.ResourceInfo{ 1125 { 1126 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"}, 1127 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1128 Path: "entry", 1129 Size: 30, 1130 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1131 }, 1132 }) 1133 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "dir"}, Path: "."}, 1134 []*sprovider.ResourceInfo{ 1135 { 1136 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "direntry"}, 1137 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1138 Path: "entry", 1139 Size: 30, 1140 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1141 }, 1142 }) 1143 1144 // For the space mounted a /foo/qux we assign a storageid "foospace" and a root opaqueid "root" 1145 // it contains one resource 1146 // - ./quux, file, 1000 bytes, opaqueid "quux" 1147 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, Path: "."}, 1148 &sprovider.ResourceInfo{ 1149 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, 1150 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 1151 Path: ".", 1152 Size: uint64(1000), 1153 }) 1154 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "root"}, Path: "."}, 1155 []*sprovider.ResourceInfo{ 1156 { 1157 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "quux"}, 1158 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1159 Path: "./quux", 1160 Size: 1000, 1161 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooquxspace", OpaqueId: "fooquxspace"}, 1162 }, 1163 }) 1164 1165 // For the space mounted a /foo/Shares/sharedFile we assign a spaceid "fooFileShareSpace" and a root opaqueid "sharedfile" 1166 // it is a file resource, 2000 bytes, opaqueid "sharedfile" 1167 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"}, Path: "."}, 1168 &sprovider.ResourceInfo{ 1169 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace", OpaqueId: "sharedfile"}, 1170 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1171 Path: ".", 1172 Size: uint64(2000), 1173 Mtime: &typesv1beta1.Timestamp{Seconds: 1}, 1174 Etag: "1", 1175 }) 1176 1177 // For the space mounted a /foo/Shares/sharedFile2 we assign a spaceid "fooFileShareSpace2" and a root opaqueid "sharedfile2" 1178 // it is a file resource, 2500 bytes, opaqueid "sharedfile2" 1179 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"}, Path: "."}, 1180 &sprovider.ResourceInfo{ 1181 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooFileShareSpace2", OpaqueId: "sharedfile2"}, 1182 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1183 Path: ".", 1184 Size: uint64(2500), 1185 Mtime: &typesv1beta1.Timestamp{Seconds: 2}, 1186 Etag: "2", 1187 }) 1188 1189 // For the space mounted a /foo/Shares/sharedFile2 we assign a spaceid "fooDirShareSpace" and a root opaqueid "shareddir" 1190 // it is a folder containing one resource 1191 // ./something, file, 1500 bytes, opaqueid "shareddirsomething" 1192 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, Path: "."}, 1193 &sprovider.ResourceInfo{ 1194 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, 1195 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 1196 Path: ".", 1197 Size: uint64(1500), 1198 Mtime: &typesv1beta1.Timestamp{Seconds: 3}, 1199 Etag: "3", 1200 }) 1201 mockListContainer(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddir"}, Path: "."}, 1202 []*sprovider.ResourceInfo{ 1203 { 1204 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "shareddirsomething"}, 1205 Type: sprovider.ResourceType_RESOURCE_TYPE_FILE, 1206 Path: "something", 1207 Size: 1500, 1208 ParentId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "fooDirShareSpace", OpaqueId: "fooDirShareSpace"}, 1209 }, 1210 }) 1211 1212 client.On("ListPublicShares", mock.Anything, mock.Anything).Return( 1213 func(_ context.Context, req *link.ListPublicSharesRequest, _ ...grpc.CallOption) *link.ListPublicSharesResponse { 1214 1215 var shares []*link.PublicShare 1216 if len(req.Filters) == 0 { 1217 shares = []*link.PublicShare{} 1218 } else { 1219 term := req.Filters[0].Term.(*link.ListPublicSharesRequest_Filter_ResourceId) 1220 switch { 1221 case term != nil && term.ResourceId != nil && term.ResourceId.OpaqueId == "bar": 1222 shares = []*link.PublicShare{ 1223 { 1224 Id: &link.PublicShareId{OpaqueId: "share1"}, 1225 ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "bar"}, 1226 }, 1227 } 1228 default: 1229 shares = []*link.PublicShare{} 1230 } 1231 } 1232 return &link.ListPublicSharesResponse{ 1233 Status: status.NewOK(ctx), 1234 Share: shares, 1235 } 1236 }, nil) 1237 }) 1238 1239 Describe("NewHandler", func() { 1240 It("returns a handler", func() { 1241 Expect(handler).ToNot(BeNil()) 1242 }) 1243 }) 1244 1245 Describe("HandlePathPropfind", func() { 1246 Context("with just one space", func() { 1247 JustBeforeEach(func() { 1248 client.On("ListStorageSpaces", mock.Anything, mock.MatchedBy(func(req *sprovider.ListStorageSpacesRequest) bool { 1249 p := string(req.Opaque.Map["path"].Value) 1250 return p == "/" || strings.HasPrefix(p, "/foo") 1251 })).Return(&sprovider.ListStorageSpacesResponse{ 1252 Status: status.NewOK(ctx), 1253 StorageSpaces: []*sprovider.StorageSpace{foospace}, 1254 }, nil) 1255 client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return(&sprovider.ListStorageSpacesResponse{ 1256 Status: status.NewOK(ctx), 1257 StorageSpaces: []*sprovider.StorageSpace{}, 1258 }, nil) 1259 mockStat(&sprovider.Reference{ResourceId: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, Path: "."}, 1260 &sprovider.ResourceInfo{ 1261 Id: &sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "foospace"}, 1262 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 1263 Path: ".", 1264 Size: uint64(131), 1265 }) 1266 }) 1267 1268 It("verifies the depth header", func() { 1269 rr := httptest.NewRecorder() 1270 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 1271 req.Header.Set(net.HeaderDepth, "invalid") 1272 req = req.WithContext(ctx) 1273 Expect(err).ToNot(HaveOccurred()) 1274 1275 handler.HandlePathPropfind(rr, req, "/") 1276 Expect(rr.Code).To(Equal(http.StatusBadRequest)) 1277 }) 1278 1279 It("stats a path", func() { 1280 rr := httptest.NewRecorder() 1281 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 1282 req = req.WithContext(ctx) 1283 Expect(err).ToNot(HaveOccurred()) 1284 1285 handler.HandlePathPropfind(rr, req, "/") 1286 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1287 1288 res, _, err := readResponse(rr.Result().Body) 1289 Expect(err).ToNot(HaveOccurred()) 1290 Expect(len(res.Responses)).To(Equal(4)) 1291 1292 root := res.Responses[0] 1293 Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/")) 1294 Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>131</oc:size>")) 1295 1296 bar := res.Responses[1] 1297 Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar")) 1298 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 1299 1300 baz := res.Responses[2] 1301 Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz")) 1302 Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>")) 1303 1304 dir := res.Responses[3] 1305 Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/")) 1306 Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>")) 1307 }) 1308 1309 It("stats a file", func() { 1310 rr := httptest.NewRecorder() 1311 req, err := http.NewRequest("GET", "/foo/bar", strings.NewReader("")) 1312 req = req.WithContext(ctx) 1313 Expect(err).ToNot(HaveOccurred()) 1314 1315 handler.HandlePathPropfind(rr, req, "/") 1316 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1317 1318 res, _, err := readResponse(rr.Result().Body) 1319 Expect(err).ToNot(HaveOccurred()) 1320 Expect(len(res.Responses)).To(Equal(1)) 1321 1322 bar := res.Responses[0] 1323 Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar")) 1324 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 1325 }) 1326 }) 1327 1328 Context("with one nested file space", func() { 1329 JustBeforeEach(func() { 1330 client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( 1331 func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { 1332 var spaces []*sprovider.StorageSpace 1333 switch string(req.Opaque.Map["path"].Value) { 1334 case "/", "/foo": 1335 spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace} 1336 case "/foo/Shares", "/foo/Shares/sharedFile": 1337 spaces = []*sprovider.StorageSpace{fooFileShareSpace} 1338 } 1339 return &sprovider.ListStorageSpacesResponse{ 1340 Status: status.NewOK(ctx), 1341 StorageSpaces: spaces, 1342 } 1343 }, 1344 nil) 1345 }) 1346 1347 It("stats the parent", func() { 1348 rr := httptest.NewRecorder() 1349 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 1350 Expect(err).ToNot(HaveOccurred()) 1351 req = req.WithContext(ctx) 1352 1353 handler.HandlePathPropfind(rr, req, "/") 1354 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1355 1356 res, _, err := readResponse(rr.Result().Body) 1357 Expect(err).ToNot(HaveOccurred()) 1358 Expect(len(res.Responses)).To(Equal(5)) 1359 1360 parent := res.Responses[0] 1361 Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/")) 1362 Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>2131</oc:size>")) 1363 1364 sf := res.Responses[4] 1365 Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/")) 1366 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>2000</oc:size>")) 1367 }) 1368 1369 It("stats the embedded space", func() { 1370 rr := httptest.NewRecorder() 1371 req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader("")) 1372 Expect(err).ToNot(HaveOccurred()) 1373 req = req.WithContext(ctx) 1374 1375 handler.HandlePathPropfind(rr, req, "/") 1376 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1377 1378 res, _, err := readResponse(rr.Result().Body) 1379 Expect(err).ToNot(HaveOccurred()) 1380 Expect(len(res.Responses)).To(Equal(1)) 1381 1382 sf := res.Responses[0] 1383 Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile")) 1384 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>2000</d:getcontentlength>")) 1385 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:01 GMT</d:getlastmodified>")) 1386 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"1"</d:getetag>`)) 1387 }) 1388 }) 1389 1390 Context("with two nested file spaces and a nested directory space", func() { 1391 JustBeforeEach(func() { 1392 client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( 1393 func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { 1394 var spaces []*sprovider.StorageSpace 1395 switch string(req.Opaque.Map["path"].Value) { 1396 case "/", "/foo": 1397 spaces = []*sprovider.StorageSpace{foospace, fooFileShareSpace, fooFileShare2Space, fooDirShareSpace} 1398 case "/foo/Shares": 1399 spaces = []*sprovider.StorageSpace{fooFileShareSpace, fooFileShare2Space, fooDirShareSpace} 1400 case "/foo/Shares/sharedFile": 1401 spaces = []*sprovider.StorageSpace{fooFileShareSpace} 1402 case "/foo/Shares/sharedFile2": 1403 spaces = []*sprovider.StorageSpace{fooFileShare2Space} 1404 } 1405 return &sprovider.ListStorageSpacesResponse{ 1406 Status: status.NewOK(ctx), 1407 StorageSpaces: spaces, 1408 } 1409 }, 1410 nil) 1411 }) 1412 1413 It("stats the parent", func() { 1414 rr := httptest.NewRecorder() 1415 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 1416 Expect(err).ToNot(HaveOccurred()) 1417 req = req.WithContext(ctx) 1418 1419 handler.HandlePathPropfind(rr, req, "/") 1420 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1421 1422 res, _, err := readResponse(rr.Result().Body) 1423 Expect(err).ToNot(HaveOccurred()) 1424 Expect(len(res.Responses)).To(Equal(5)) 1425 1426 parent := res.Responses[0] 1427 Expect(parent.Href).To(Equal("http:/127.0.0.1:3000/foo/")) 1428 Expect(string(parent.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>6131</oc:size>")) 1429 1430 shares := res.Responses[4] 1431 Expect(shares.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/")) 1432 Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>6000</oc:size>")) 1433 Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getlastmodified>Thu, 01 Jan 1970 00:00:03 GMT</d:getlastmodified>")) 1434 Expect(string(shares.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring(`<d:getetag>"3"</d:getetag>`)) 1435 }) 1436 1437 It("stats the embedded space", func() { 1438 rr := httptest.NewRecorder() 1439 req, err := http.NewRequest("GET", "/foo/Shares/sharedFile", strings.NewReader("")) 1440 Expect(err).ToNot(HaveOccurred()) 1441 req = req.WithContext(ctx) 1442 1443 handler.HandlePathPropfind(rr, req, "/") 1444 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1445 1446 res, _, err := readResponse(rr.Result().Body) 1447 Expect(err).ToNot(HaveOccurred()) 1448 Expect(len(res.Responses)).To(Equal(1)) 1449 1450 sf := res.Responses[0] 1451 Expect(sf.Href).To(Equal("http:/127.0.0.1:3000/foo/Shares/sharedFile")) 1452 Expect(string(sf.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>2000</d:getcontentlength>")) 1453 }) 1454 1455 It("includes all the things™ when depth is infinity", func() { 1456 rr := httptest.NewRecorder() 1457 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 1458 Expect(err).ToNot(HaveOccurred()) 1459 req = req.WithContext(ctx) 1460 req.Header.Add(net.HeaderDepth, "infinity") 1461 1462 handler.HandlePathPropfind(rr, req, "/") 1463 Expect(rr.Code).To(Equal(http.StatusBadRequest)) 1464 }) 1465 }) 1466 1467 Context("with a nested directory space", func() { 1468 JustBeforeEach(func() { 1469 client.On("ListStorageSpaces", mock.Anything, mock.Anything).Return( 1470 func(_ context.Context, req *sprovider.ListStorageSpacesRequest, _ ...grpc.CallOption) *sprovider.ListStorageSpacesResponse { 1471 var spaces []*sprovider.StorageSpace 1472 switch string(req.Opaque.Map["path"].Value) { 1473 case "/", "/foo": 1474 spaces = []*sprovider.StorageSpace{foospace, fooquxspace} 1475 case "/foo/qux": 1476 spaces = []*sprovider.StorageSpace{fooquxspace} 1477 } 1478 return &sprovider.ListStorageSpacesResponse{ 1479 Status: status.NewOK(ctx), 1480 StorageSpaces: spaces, 1481 } 1482 }, 1483 nil) 1484 }) 1485 1486 // Pending, the code for handling missing parents is still missing 1487 PIt("handles children with no parent", func() { 1488 rr := httptest.NewRecorder() 1489 req, err := http.NewRequest("GET", "/", strings.NewReader("")) 1490 Expect(err).ToNot(HaveOccurred()) 1491 req = req.WithContext(ctx) 1492 1493 handler.HandlePathPropfind(rr, req, "/") 1494 Expect(rr.Code).To(Equal(http.StatusOK)) 1495 }) 1496 1497 It("mounts embedded spaces", func() { 1498 rr := httptest.NewRecorder() 1499 req, err := http.NewRequest("GET", "/foo", strings.NewReader("")) 1500 Expect(err).ToNot(HaveOccurred()) 1501 req = req.WithContext(ctx) 1502 1503 handler.HandlePathPropfind(rr, req, "/") 1504 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1505 1506 res, _, err := readResponse(rr.Result().Body) 1507 Expect(err).ToNot(HaveOccurred()) 1508 Expect(len(res.Responses)).To(Equal(5)) 1509 1510 root := res.Responses[0] 1511 Expect(root.Href).To(Equal("http:/127.0.0.1:3000/foo/")) 1512 Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1131</oc:size>")) 1513 1514 bar := res.Responses[1] 1515 Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/foo/bar")) 1516 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 1517 1518 baz := res.Responses[2] 1519 Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/foo/baz")) 1520 Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>")) 1521 1522 dir := res.Responses[3] 1523 Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/foo/dir/")) 1524 Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>")) 1525 1526 qux := res.Responses[4] 1527 Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/")) 1528 Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1000</oc:size>")) 1529 }) 1530 1531 It("stats the embedded space", func() { 1532 rr := httptest.NewRecorder() 1533 req, err := http.NewRequest("GET", "/foo/qux/", strings.NewReader("")) 1534 Expect(err).ToNot(HaveOccurred()) 1535 req = req.WithContext(ctx) 1536 1537 handler.HandlePathPropfind(rr, req, "/") 1538 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1539 1540 res, _, err := readResponse(rr.Result().Body) 1541 Expect(err).ToNot(HaveOccurred()) 1542 Expect(len(res.Responses)).To(Equal(2)) 1543 1544 qux := res.Responses[0] 1545 Expect(qux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/")) 1546 Expect(string(qux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>1000</oc:size>")) 1547 1548 quux := res.Responses[1] 1549 Expect(quux.Href).To(Equal("http:/127.0.0.1:3000/foo/qux/quux")) 1550 Expect(string(quux.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1000</d:getcontentlength>")) 1551 }) 1552 }) 1553 }) 1554 1555 Describe("HandleSpacesPropfind", func() { 1556 /* 1557 JustBeforeEach(func() { 1558 client.On("Stat", mock.Anything, mock.Anything).Return(func(_ context.Context, req *sprovider.StatRequest, _ ...grpc.CallOption) *sprovider.StatResponse { 1559 switch { 1560 case req.Ref.ResourceId.OpaqueId == "foospace": 1561 return &sprovider.StatResponse{ 1562 Status: status.NewOK(ctx), 1563 Info: &sprovider.ResourceInfo{ 1564 Type: sprovider.ResourceType_RESOURCE_TYPE_CONTAINER, 1565 Id: &sprovider.ResourceId{OpaqueId: "foospaceroot", StorageId: "foospaceroot"}, 1566 Size: 131, 1567 Path: ".", 1568 }, 1569 } 1570 default: 1571 return &sprovider.StatResponse{ 1572 Status: status.NewNotFound(ctx, "not found"), 1573 } 1574 } 1575 }, nil) 1576 }) 1577 */ 1578 1579 It("handles invalid space ids", func() { 1580 client.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{ 1581 Status: status.NewNotFound(ctx, "not found"), 1582 }, nil) 1583 1584 rr := httptest.NewRecorder() 1585 req, err := http.NewRequest("GET", "/", strings.NewReader("")) 1586 Expect(err).ToNot(HaveOccurred()) 1587 1588 handler.HandleSpacesPropfind(rr, req, "does-not-exist") 1589 Expect(rr.Code).To(Equal(http.StatusNotFound)) 1590 }) 1591 1592 It("stats the space root", func() { 1593 client.On("Stat", mock.Anything, mock.Anything).Return(&sprovider.StatResponse{ 1594 Status: status.NewNotFound(ctx, "not found"), 1595 }, nil) 1596 rr := httptest.NewRecorder() 1597 req, err := http.NewRequest("GET", "/", strings.NewReader("")) 1598 Expect(err).ToNot(HaveOccurred()) 1599 req = req.WithContext(ctx) 1600 1601 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 1602 spaceIDUrl := net.EncodePath(spaceID) 1603 handler.HandleSpacesPropfind(rr, req, spaceID) 1604 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1605 1606 res, _, err := readResponse(rr.Result().Body) 1607 Expect(err).ToNot(HaveOccurred()) 1608 Expect(len(res.Responses)).To(Equal(4)) 1609 1610 root := res.Responses[0] 1611 Expect(root.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/")) 1612 Expect(string(root.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>131</oc:size>")) 1613 1614 bar := res.Responses[1] 1615 Expect(bar.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/bar")) 1616 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 1617 Expect(string(bar.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>")) 1618 1619 baz := res.Responses[2] 1620 Expect(baz.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/baz")) 1621 Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>1</d:getcontentlength>")) 1622 Expect(string(baz.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>")) 1623 1624 dir := res.Responses[3] 1625 Expect(dir.Href).To(Equal("http:/127.0.0.1:3000/" + spaceIDUrl + "/dir/")) 1626 Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>")) 1627 Expect(string(dir.Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:file-parent>provider-1$foospace!foospace</oc:file-parent>")) 1628 }) 1629 1630 It("stats a file", func() { 1631 rr := httptest.NewRecorder() 1632 req, err := http.NewRequest("GET", "/bar", strings.NewReader("")) 1633 Expect(err).ToNot(HaveOccurred()) 1634 req = req.WithContext(ctx) 1635 1636 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 1637 handler.HandleSpacesPropfind(rr, req, spaceID) 1638 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1639 1640 res, _, err := readResponse(rr.Result().Body) 1641 Expect(err).ToNot(HaveOccurred()) 1642 Expect(len(res.Responses)).To(Equal(1)) 1643 Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>100</d:getcontentlength>")) 1644 }) 1645 1646 It("stats a directory", func() { 1647 rr := httptest.NewRecorder() 1648 req, err := http.NewRequest("GET", "/dir", strings.NewReader("")) 1649 Expect(err).ToNot(HaveOccurred()) 1650 req = req.WithContext(ctx) 1651 1652 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 1653 handler.HandleSpacesPropfind(rr, req, spaceID) 1654 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1655 1656 res, _, err := readResponse(rr.Result().Body) 1657 Expect(err).ToNot(HaveOccurred()) 1658 Expect(len(res.Responses)).To(Equal(2)) 1659 Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:size>30</oc:size>")) 1660 Expect(string(res.Responses[1].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>30</d:getcontentlength>")) 1661 }) 1662 1663 It("stats a directory with xml special characters", func() { 1664 rr := httptest.NewRecorder() 1665 req, err := http.NewRequest("GET", "/dir&dir", strings.NewReader("")) 1666 Expect(err).ToNot(HaveOccurred()) 1667 req = req.WithContext(ctx) 1668 1669 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 1670 handler.HandleSpacesPropfind(rr, req, spaceID) 1671 Expect(rr.Code).To(Equal(http.StatusMultiStatus)) 1672 1673 res, _, err := readResponse(rr.Result().Body) 1674 Expect(err).ToNot(HaveOccurred()) 1675 Expect(len(res.Responses)).To(Equal(2)) 1676 Expect(string(res.Responses[0].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<oc:name>dir&dir</oc:name>")) 1677 Expect(string(res.Responses[1].Propstat[0].Prop[0].InnerXML)).To(ContainSubstring("<d:getcontentlength>30</d:getcontentlength>")) 1678 }) 1679 1680 It("includes all the things™ when depth is infinity", func() { 1681 rr := httptest.NewRecorder() 1682 req, err := http.NewRequest("GET", "/", strings.NewReader("")) 1683 req.Header.Add(net.HeaderDepth, "infinity") 1684 Expect(err).ToNot(HaveOccurred()) 1685 req = req.WithContext(ctx) 1686 1687 spaceID := storagespace.FormatResourceID(&sprovider.ResourceId{StorageId: "provider-1", SpaceId: "foospace", OpaqueId: "root"}) 1688 handler.HandleSpacesPropfind(rr, req, spaceID) 1689 Expect(rr.Code).To(Equal(http.StatusBadRequest)) 1690 1691 _, _, err = readResponse(rr.Result().Body) 1692 Expect(err).To(HaveOccurred()) 1693 1694 }) 1695 }) 1696 })