github.com/xzl8028/xenia-server@v0.0.0-20190809101854-18450a97da63/api4/file_test.go (about) 1 // Copyright (c) 2017-present Xenia, Inc. All Rights Reserved. 2 // See License.txt for license information. 3 4 package api4 5 6 import ( 7 "bytes" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "mime/multipart" 12 "net/http" 13 "net/textproto" 14 "net/url" 15 "os" 16 "path/filepath" 17 "strings" 18 "testing" 19 "time" 20 21 "github.com/xzl8028/xenia-server/app" 22 "github.com/xzl8028/xenia-server/model" 23 "github.com/xzl8028/xenia-server/utils/fileutils" 24 "github.com/xzl8028/xenia-server/utils/testutils" 25 "github.com/stretchr/testify/require" 26 ) 27 28 var testDir = "" 29 30 func init() { 31 testDir, _ = fileutils.FindDir("tests") 32 } 33 34 func checkCond(tb testing.TB, cond bool, text string) { 35 if !cond { 36 tb.Error(text) 37 } 38 } 39 40 func checkEq(tb testing.TB, v1, v2 interface{}, text string) { 41 checkCond(tb, fmt.Sprintf("%+v", v1) == fmt.Sprintf("%+v", v2), text) 42 } 43 44 func checkNeq(tb testing.TB, v1, v2 interface{}, text string) { 45 checkCond(tb, fmt.Sprintf("%+v", v1) != fmt.Sprintf("%+v", v2), text) 46 } 47 48 type zeroReader struct { 49 limit, read int 50 } 51 52 func (z *zeroReader) Read(b []byte) (int, error) { 53 for i := range b { 54 if z.read == z.limit { 55 return i, io.EOF 56 } 57 b[i] = 0 58 z.read++ 59 } 60 61 return len(b), nil 62 } 63 64 // File Section 65 var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") 66 67 func escapeQuotes(s string) string { 68 return quoteEscaper.Replace(s) 69 } 70 71 type UploadOpener func() (io.ReadCloser, int64, error) 72 73 func NewUploadOpenerReader(in io.Reader) UploadOpener { 74 return func() (io.ReadCloser, int64, error) { 75 rc, ok := in.(io.ReadCloser) 76 if ok { 77 return rc, -1, nil 78 } else { 79 return ioutil.NopCloser(in), -1, nil 80 } 81 } 82 } 83 84 func NewUploadOpenerFile(path string) UploadOpener { 85 return func() (io.ReadCloser, int64, error) { 86 fi, err := os.Stat(path) 87 if err != nil { 88 return nil, 0, err 89 } 90 f, err := os.Open(path) 91 if err != nil { 92 return nil, 0, err 93 } 94 return f, fi.Size(), nil 95 } 96 } 97 98 // testUploadFile and testUploadFiles have been "staged" here, eventually they 99 // should move back to being model.Client4 methods, once the specifics of the 100 // public API are sorted out. 101 func testUploadFile(c *model.Client4, url string, body io.Reader, contentType string, 102 contentLength int64) (*model.FileUploadResponse, *model.Response) { 103 rq, _ := http.NewRequest("POST", c.ApiUrl+c.GetFilesRoute()+url, body) 104 if contentLength != 0 { 105 rq.ContentLength = contentLength 106 } 107 rq.Header.Set("Content-Type", contentType) 108 109 if len(c.AuthToken) > 0 { 110 rq.Header.Set(model.HEADER_AUTH, c.AuthType+" "+c.AuthToken) 111 } 112 113 rp, err := c.HttpClient.Do(rq) 114 if err != nil || rp == nil { 115 return nil, model.BuildErrorResponse(rp, model.NewAppError(url, "model.client.connecting.app_error", nil, err.Error(), 0)) 116 } 117 defer closeBody(rp) 118 119 if rp.StatusCode >= 300 { 120 return nil, model.BuildErrorResponse(rp, model.AppErrorFromJson(rp.Body)) 121 } 122 123 return model.FileUploadResponseFromJson(rp.Body), model.BuildResponse(rp) 124 } 125 126 func testUploadFiles( 127 c *model.Client4, 128 channelId string, 129 names []string, 130 openers []UploadOpener, 131 contentLengths []int64, 132 clientIds []string, 133 useMultipart, 134 useChunkedInSimplePost bool, 135 ) ( 136 fileUploadResponse *model.FileUploadResponse, 137 response *model.Response, 138 ) { 139 // Do not check len(clientIds), leave it entirely to the user to 140 // provide. The server will error out if it does not match the number 141 // of files, but it's not critical here. 142 if len(names) == 0 || len(openers) == 0 || len(names) != len(openers) { 143 return nil, &model.Response{ 144 Error: model.NewAppError("testUploadFiles", 145 "model.client.upload_post_attachment.file.app_error", 146 nil, "Empty or mismatched file data", http.StatusBadRequest), 147 } 148 } 149 150 // emergencyResponse is a convenience wrapper to return an error response 151 emergencyResponse := func(err error, errCode string) *model.Response { 152 return &model.Response{ 153 Error: model.NewAppError("testUploadFiles", 154 "model.client."+errCode+".app_error", 155 nil, err.Error(), http.StatusBadRequest), 156 } 157 } 158 159 // For multipart, start writing the request as a goroutine, and pipe 160 // multipart.Writer into it, otherwise generate a new request each 161 // time. 162 pipeReader, pipeWriter := io.Pipe() 163 mw := multipart.NewWriter(pipeWriter) 164 165 if useMultipart { 166 fileUploadResponseChannel := make(chan *model.FileUploadResponse) 167 responseChannel := make(chan *model.Response) 168 closedMultipart := false 169 170 go func() { 171 fur, resp := testUploadFile(c, "", pipeReader, mw.FormDataContentType(), -1) 172 responseChannel <- resp 173 fileUploadResponseChannel <- fur 174 }() 175 176 defer func() { 177 for { 178 select { 179 // Premature response, before the entire 180 // multipart was sent 181 case response = <-responseChannel: 182 // Guaranteed to be there 183 fileUploadResponse = <-fileUploadResponseChannel 184 if !closedMultipart { 185 _ = mw.Close() 186 _ = pipeWriter.Close() 187 closedMultipart = true 188 } 189 return 190 191 // Normal response, after the multipart was sent. 192 default: 193 if !closedMultipart { 194 err := mw.Close() 195 if err != nil { 196 fileUploadResponse = nil 197 response = emergencyResponse(err, "upload_post_attachment.writer") 198 return 199 } 200 err = pipeWriter.Close() 201 if err != nil { 202 fileUploadResponse = nil 203 response = emergencyResponse(err, "upload_post_attachment.writer") 204 return 205 } 206 closedMultipart = true 207 } 208 } 209 } 210 }() 211 212 err := mw.WriteField("channel_id", channelId) 213 if err != nil { 214 return nil, emergencyResponse(err, "upload_post_attachment.channel_id") 215 } 216 } else { 217 fileUploadResponse = &model.FileUploadResponse{} 218 } 219 220 data := make([]byte, 512) 221 222 upload := func(i int, f io.ReadCloser) *model.Response { 223 var cl int64 224 defer f.Close() 225 226 if len(contentLengths) > i { 227 cl = contentLengths[i] 228 } 229 230 n, err := f.Read(data) 231 if err != nil && err != io.EOF { 232 return emergencyResponse(err, "upload_post_attachment") 233 } 234 ct := http.DetectContentType(data[:n]) 235 reader := io.MultiReader(bytes.NewReader(data[:n]), f) 236 237 if useMultipart { 238 if len(clientIds) > i { 239 err := mw.WriteField("client_ids", clientIds[i]) 240 if err != nil { 241 return emergencyResponse(err, "upload_post_attachment.file") 242 } 243 } 244 245 h := make(textproto.MIMEHeader) 246 h.Set("Content-Disposition", 247 fmt.Sprintf(`form-data; name="files"; filename="%s"`, escapeQuotes(names[i]))) 248 h.Set("Content-Type", ct) 249 250 // If we error here, writing to mw, the deferred handler 251 part, err := mw.CreatePart(h) 252 if err != nil { 253 return emergencyResponse(err, "upload_post_attachment.writer") 254 } 255 256 _, err = io.Copy(part, reader) 257 if err != nil { 258 return emergencyResponse(err, "upload_post_attachment.writer") 259 } 260 } else { 261 postURL := fmt.Sprintf("?channel_id=%v", url.QueryEscape(channelId)) + 262 fmt.Sprintf("&filename=%v", url.QueryEscape(names[i])) 263 if len(clientIds) > i { 264 postURL += fmt.Sprintf("&client_id=%v", url.QueryEscape(clientIds[i])) 265 } 266 if useChunkedInSimplePost { 267 cl = -1 268 } 269 fur, resp := testUploadFile(c, postURL, reader, ct, cl) 270 if resp.Error != nil { 271 return resp 272 } 273 fileUploadResponse.FileInfos = append(fileUploadResponse.FileInfos, fur.FileInfos[0]) 274 if len(clientIds) > 0 { 275 if len(fur.ClientIds) > 0 { 276 fileUploadResponse.ClientIds = append(fileUploadResponse.ClientIds, fur.ClientIds[0]) 277 } else { 278 fileUploadResponse.ClientIds = append(fileUploadResponse.ClientIds, "") 279 } 280 } 281 response = resp 282 } 283 284 return nil 285 } 286 287 for i, open := range openers { 288 f, _, err := open() 289 if err != nil { 290 return nil, emergencyResponse(err, "upload_post_attachment") 291 } 292 293 resp := upload(i, f) 294 if resp != nil && resp.Error != nil { 295 return nil, resp 296 } 297 } 298 299 // In case of a simple POST, the return values have been set by upload(), 300 // otherwise we finished writing the multipart, and the return values will 301 // be set in defer 302 return fileUploadResponse, response 303 } 304 305 func TestUploadFiles(t *testing.T) { 306 th := Setup().InitBasic() 307 defer th.TearDown() 308 if *th.App.Config().FileSettings.DriverName == "" { 309 t.Skip("skipping because no file driver is enabled") 310 } 311 312 channel := th.BasicChannel 313 date := time.Now().Format("20060102") 314 315 // Get better error messages 316 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = true }) 317 318 op := func(name string) UploadOpener { 319 return NewUploadOpenerFile(filepath.Join(testDir, name)) 320 } 321 322 tests := []struct { 323 title string 324 client *model.Client4 325 openers []UploadOpener 326 names []string 327 clientIds []string 328 329 skipSuccessValidation bool 330 skipPayloadValidation bool 331 skipSimplePost bool 332 skipMultipart bool 333 channelId string 334 useChunkedInSimplePost bool 335 expectedCreatorId string 336 expectedPayloadNames []string 337 expectImage bool 338 expectedImageWidths []int 339 expectedImageHeights []int 340 expectedImageThumbnailNames []string 341 expectedImagePreviewNames []string 342 expectedImageHasPreview []bool 343 setupConfig func(a *app.App) func(a *app.App) 344 checkResponse func(t *testing.T, resp *model.Response) 345 }{ 346 // Upload a bunch of files, mixed images and non-images 347 { 348 title: "Happy", 349 names: []string{"test.png", "testgif.gif", "testplugin.tar.gz", "test-search.md", "test.tiff"}, 350 expectedCreatorId: th.BasicUser.Id, 351 }, 352 // Upload a bunch of files, with clientIds 353 { 354 title: "Happy client_ids", 355 names: []string{"test.png", "testgif.gif", "testplugin.tar.gz", "test-search.md", "test.tiff"}, 356 clientIds: []string{"1", "2", "3", "4", "5"}, 357 expectedCreatorId: th.BasicUser.Id, 358 }, 359 // Upload a bunch of images. testgif.gif is an animated GIF, 360 // so it does not have HasPreviewImage set. 361 { 362 title: "Happy images", 363 names: []string{"test.png", "testgif.gif"}, 364 expectImage: true, 365 expectedCreatorId: th.BasicUser.Id, 366 expectedImageWidths: []int{408, 118}, 367 expectedImageHeights: []int{336, 118}, 368 expectedImageHasPreview: []bool{true, false}, 369 }, 370 { 371 title: "Happy invalid image", 372 names: []string{"testgif.gif"}, 373 openers: []UploadOpener{NewUploadOpenerFile(filepath.Join(testDir, "test-search.md"))}, 374 skipPayloadValidation: true, 375 expectedCreatorId: th.BasicUser.Id, 376 }, 377 // Simple POST, chunked encoding 378 { 379 title: "Happy image chunked post", 380 skipMultipart: true, 381 useChunkedInSimplePost: true, 382 names: []string{"test.png"}, 383 expectImage: true, 384 expectedImageWidths: []int{408}, 385 expectedImageHeights: []int{336}, 386 expectedImageHasPreview: []bool{true}, 387 expectedCreatorId: th.BasicUser.Id, 388 }, 389 // Image thumbnail and preview: size and orientation. Note that 390 // the expected image dimensions remain the same regardless of the 391 // orientation - what we save in FileInfo is used by the 392 // clients to size UI elements, so the dimensions are "actual". 393 { 394 title: "Happy image thumbnail/preview 1", 395 names: []string{"orientation_test_1.jpeg"}, 396 expectedImageThumbnailNames: []string{"orientation_test_1_expected_thumb.jpeg"}, 397 expectedImagePreviewNames: []string{"orientation_test_1_expected_preview.jpeg"}, 398 expectImage: true, 399 expectedImageWidths: []int{2860}, 400 expectedImageHeights: []int{1578}, 401 expectedImageHasPreview: []bool{true}, 402 expectedCreatorId: th.BasicUser.Id, 403 }, 404 { 405 title: "Happy image thumbnail/preview 2", 406 names: []string{"orientation_test_2.jpeg"}, 407 expectedImageThumbnailNames: []string{"orientation_test_2_expected_thumb.jpeg"}, 408 expectedImagePreviewNames: []string{"orientation_test_2_expected_preview.jpeg"}, 409 expectImage: true, 410 expectedImageWidths: []int{2860}, 411 expectedImageHeights: []int{1578}, 412 expectedImageHasPreview: []bool{true}, 413 expectedCreatorId: th.BasicUser.Id, 414 }, 415 { 416 title: "Happy image thumbnail/preview 3", 417 names: []string{"orientation_test_3.jpeg"}, 418 expectedImageThumbnailNames: []string{"orientation_test_3_expected_thumb.jpeg"}, 419 expectedImagePreviewNames: []string{"orientation_test_3_expected_preview.jpeg"}, 420 expectImage: true, 421 expectedImageWidths: []int{2860}, 422 expectedImageHeights: []int{1578}, 423 expectedImageHasPreview: []bool{true}, 424 expectedCreatorId: th.BasicUser.Id, 425 }, 426 { 427 title: "Happy image thumbnail/preview 4", 428 names: []string{"orientation_test_4.jpeg"}, 429 expectedImageThumbnailNames: []string{"orientation_test_4_expected_thumb.jpeg"}, 430 expectedImagePreviewNames: []string{"orientation_test_4_expected_preview.jpeg"}, 431 expectImage: true, 432 expectedImageWidths: []int{2860}, 433 expectedImageHeights: []int{1578}, 434 expectedImageHasPreview: []bool{true}, 435 expectedCreatorId: th.BasicUser.Id, 436 }, 437 { 438 title: "Happy image thumbnail/preview 5", 439 names: []string{"orientation_test_5.jpeg"}, 440 expectedImageThumbnailNames: []string{"orientation_test_5_expected_thumb.jpeg"}, 441 expectedImagePreviewNames: []string{"orientation_test_5_expected_preview.jpeg"}, 442 expectImage: true, 443 expectedImageWidths: []int{2860}, 444 expectedImageHeights: []int{1578}, 445 expectedImageHasPreview: []bool{true}, 446 expectedCreatorId: th.BasicUser.Id, 447 }, 448 { 449 title: "Happy image thumbnail/preview 6", 450 names: []string{"orientation_test_6.jpeg"}, 451 expectedImageThumbnailNames: []string{"orientation_test_6_expected_thumb.jpeg"}, 452 expectedImagePreviewNames: []string{"orientation_test_6_expected_preview.jpeg"}, 453 expectImage: true, 454 expectedImageWidths: []int{2860}, 455 expectedImageHeights: []int{1578}, 456 expectedImageHasPreview: []bool{true}, 457 expectedCreatorId: th.BasicUser.Id, 458 }, 459 { 460 title: "Happy image thumbnail/preview 7", 461 names: []string{"orientation_test_7.jpeg"}, 462 expectedImageThumbnailNames: []string{"orientation_test_7_expected_thumb.jpeg"}, 463 expectedImagePreviewNames: []string{"orientation_test_7_expected_preview.jpeg"}, 464 expectImage: true, 465 expectedImageWidths: []int{2860}, 466 expectedImageHeights: []int{1578}, 467 expectedImageHasPreview: []bool{true}, 468 expectedCreatorId: th.BasicUser.Id, 469 }, 470 { 471 title: "Happy image thumbnail/preview 8", 472 names: []string{"orientation_test_8.jpeg"}, 473 expectedImageThumbnailNames: []string{"orientation_test_8_expected_thumb.jpeg"}, 474 expectedImagePreviewNames: []string{"orientation_test_8_expected_preview.jpeg"}, 475 expectImage: true, 476 expectedImageWidths: []int{2860}, 477 expectedImageHeights: []int{1578}, 478 expectedImageHasPreview: []bool{true}, 479 expectedCreatorId: th.BasicUser.Id, 480 }, 481 // TIFF preview test 482 { 483 title: "Happy image thumbnail/preview 9", 484 names: []string{"test.tiff"}, 485 expectedImageThumbnailNames: []string{"test_expected_thumb.tiff"}, 486 expectedImagePreviewNames: []string{"test_expected_preview.tiff"}, 487 expectImage: true, 488 expectedImageWidths: []int{701}, 489 expectedImageHeights: []int{701}, 490 expectedImageHasPreview: []bool{true}, 491 expectedCreatorId: th.BasicUser.Id, 492 }, 493 { 494 title: "Happy admin", 495 client: th.SystemAdminClient, 496 names: []string{"test.png"}, 497 expectedCreatorId: th.SystemAdminUser.Id, 498 }, 499 { 500 title: "Happy stream", 501 useChunkedInSimplePost: true, 502 skipPayloadValidation: true, 503 names: []string{"50Mb-stream"}, 504 openers: []UploadOpener{NewUploadOpenerReader(&zeroReader{limit: 50 * 1024 * 1024})}, 505 setupConfig: func(a *app.App) func(a *app.App) { 506 maxFileSize := *a.Config().FileSettings.MaxFileSize 507 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 50 * 1024 * 1024 }) 508 return func(a *app.App) { 509 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize }) 510 } 511 }, 512 expectedCreatorId: th.BasicUser.Id, 513 }, 514 // Error cases 515 { 516 title: "Error channel_id does not exist", 517 channelId: model.NewId(), 518 names: []string{"test.png"}, 519 skipSuccessValidation: true, 520 checkResponse: CheckForbiddenStatus, 521 }, 522 { 523 // on simple post this uploads the last file 524 // successfully, without a ClientId 525 title: "Error too few client_ids", 526 skipSimplePost: true, 527 names: []string{"test.png", "testplugin.tar.gz", "test-search.md"}, 528 clientIds: []string{"1", "4"}, 529 skipSuccessValidation: true, 530 checkResponse: CheckBadRequestStatus, 531 }, 532 { 533 title: "Error invalid channel_id", 534 channelId: "../../junk", 535 names: []string{"test.png"}, 536 skipSuccessValidation: true, 537 checkResponse: CheckBadRequestStatus, 538 }, 539 { 540 title: "Error admin channel_id does not exist", 541 client: th.SystemAdminClient, 542 channelId: model.NewId(), 543 names: []string{"test.png"}, 544 skipSuccessValidation: true, 545 checkResponse: CheckForbiddenStatus, 546 }, 547 { 548 title: "Error admin invalid channel_id", 549 client: th.SystemAdminClient, 550 channelId: "../../junk", 551 names: []string{"test.png"}, 552 skipSuccessValidation: true, 553 checkResponse: CheckBadRequestStatus, 554 }, 555 { 556 title: "Error admin disabled uploads", 557 client: th.SystemAdminClient, 558 names: []string{"test.png"}, 559 skipSuccessValidation: true, 560 checkResponse: CheckNotImplementedStatus, 561 setupConfig: func(a *app.App) func(a *app.App) { 562 enableFileAttachments := *a.Config().FileSettings.EnableFileAttachments 563 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnableFileAttachments = false }) 564 return func(a *app.App) { 565 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnableFileAttachments = enableFileAttachments }) 566 } 567 }, 568 }, 569 { 570 title: "Error file too large", 571 names: []string{"test.png"}, 572 skipSuccessValidation: true, 573 checkResponse: CheckRequestEntityTooLargeStatus, 574 setupConfig: func(a *app.App) func(a *app.App) { 575 maxFileSize := *a.Config().FileSettings.MaxFileSize 576 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 279590 }) 577 return func(a *app.App) { 578 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize }) 579 } 580 }, 581 }, 582 // File too large (chunked, simple POST only, multipart would've been redundant with above) 583 { 584 title: "File too large chunked", 585 useChunkedInSimplePost: true, 586 skipMultipart: true, 587 names: []string{"test.png"}, 588 skipSuccessValidation: true, 589 checkResponse: CheckRequestEntityTooLargeStatus, 590 setupConfig: func(a *app.App) func(a *app.App) { 591 maxFileSize := *a.Config().FileSettings.MaxFileSize 592 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 279590 }) 593 return func(a *app.App) { 594 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize }) 595 } 596 }, 597 }, 598 { 599 title: "Error stream too large", 600 skipPayloadValidation: true, 601 names: []string{"50Mb-stream"}, 602 openers: []UploadOpener{NewUploadOpenerReader(&zeroReader{limit: 50 * 1024 * 1024})}, 603 skipSuccessValidation: true, 604 checkResponse: CheckRequestEntityTooLargeStatus, 605 setupConfig: func(a *app.App) func(a *app.App) { 606 maxFileSize := *a.Config().FileSettings.MaxFileSize 607 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 100 * 1024 }) 608 return func(a *app.App) { 609 a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize }) 610 } 611 }, 612 }, 613 } 614 615 for _, useMultipart := range []bool{true, false} { 616 for _, tc := range tests { 617 if tc.skipMultipart && useMultipart || tc.skipSimplePost && !useMultipart { 618 continue 619 } 620 621 // Set the default values and title 622 client := th.Client 623 if tc.client != nil { 624 client = tc.client 625 } 626 channelId := channel.Id 627 if tc.channelId != "" { 628 channelId = tc.channelId 629 } 630 if tc.checkResponse == nil { 631 tc.checkResponse = CheckNoError 632 } 633 634 title := "" 635 if useMultipart { 636 title = "multipart " 637 } else { 638 title = "simple " 639 } 640 if tc.title != "" { 641 title += tc.title + " " 642 } 643 title += fmt.Sprintf("%v", tc.names) 644 645 // Apply any necessary config changes 646 var restoreConfig func(a *app.App) 647 if tc.setupConfig != nil { 648 restoreConfig = tc.setupConfig(th.App) 649 } 650 651 t.Run(title, func(t *testing.T) { 652 if len(tc.openers) == 0 { 653 for _, name := range tc.names { 654 tc.openers = append(tc.openers, op(name)) 655 } 656 } 657 fileResp, resp := testUploadFiles(client, channelId, tc.names, 658 tc.openers, nil, tc.clientIds, useMultipart, 659 tc.useChunkedInSimplePost) 660 tc.checkResponse(t, resp) 661 if tc.skipSuccessValidation { 662 return 663 } 664 665 if fileResp == nil || len(fileResp.FileInfos) == 0 || len(fileResp.FileInfos) != len(tc.names) { 666 t.Fatal("Empty or mismatched actual or expected FileInfos") 667 } 668 669 for i, ri := range fileResp.FileInfos { 670 // The returned file info from the upload call will be missing some fields that will be stored in the database 671 checkEq(t, ri.CreatorId, tc.expectedCreatorId, "File should be assigned to user") 672 checkEq(t, ri.PostId, "", "File shouldn't have a post Id") 673 checkEq(t, ri.Path, "", "File path should not be set on returned info") 674 checkEq(t, ri.ThumbnailPath, "", "File thumbnail path should not be set on returned info") 675 checkEq(t, ri.PreviewPath, "", "File preview path should not be set on returned info") 676 if len(tc.clientIds) > i { 677 checkCond(t, len(fileResp.ClientIds) == len(tc.clientIds), 678 fmt.Sprintf("Wrong number of clientIds returned, expected %v, got %v", len(tc.clientIds), len(fileResp.ClientIds))) 679 checkEq(t, fileResp.ClientIds[i], tc.clientIds[i], 680 fmt.Sprintf("Wrong clientId returned, expected %v, got %v", tc.clientIds[i], fileResp.ClientIds[i])) 681 } 682 683 dbInfo, err := th.App.Srv.Store.FileInfo().Get(ri.Id) 684 require.Nil(t, err) 685 checkEq(t, dbInfo.Id, ri.Id, "File id from response should match one stored in database") 686 checkEq(t, dbInfo.CreatorId, tc.expectedCreatorId, "F ile should be assigned to user") 687 checkEq(t, dbInfo.PostId, "", "File shouldn't have a post") 688 checkNeq(t, dbInfo.Path, "", "File path should be set in database") 689 _, fname := filepath.Split(dbInfo.Path) 690 ext := filepath.Ext(fname) 691 name := fname[:len(fname)-len(ext)] 692 expectedDir := fmt.Sprintf("%v/teams/%v/channels/%v/users/%s/%s", date, FILE_TEAM_ID, channel.Id, ri.CreatorId, ri.Id) 693 expectedPath := fmt.Sprintf("%s/%s", expectedDir, fname) 694 checkEq(t, dbInfo.Path, expectedPath, 695 fmt.Sprintf("File %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.Path, expectedPath)) 696 697 if tc.expectImage { 698 expectedThumbnailPath := fmt.Sprintf("%s/%s_thumb.jpg", expectedDir, name) 699 expectedPreviewPath := fmt.Sprintf("%s/%s_preview.jpg", expectedDir, name) 700 checkEq(t, dbInfo.ThumbnailPath, expectedThumbnailPath, 701 fmt.Sprintf("Thumbnail for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.ThumbnailPath, expectedThumbnailPath)) 702 checkEq(t, dbInfo.PreviewPath, expectedPreviewPath, 703 fmt.Sprintf("Preview for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.PreviewPath, expectedPreviewPath)) 704 705 checkCond(t, 706 dbInfo.HasPreviewImage == tc.expectedImageHasPreview[i], 707 fmt.Sprintf("Image: HasPreviewImage should be set for %s", dbInfo.Name)) 708 checkCond(t, 709 dbInfo.Width == tc.expectedImageWidths[i] && dbInfo.Height == tc.expectedImageHeights[i], 710 fmt.Sprintf("Image dimensions: expected %dwx%dh, got %vwx%dh", 711 tc.expectedImageWidths[i], tc.expectedImageHeights[i], 712 dbInfo.Width, dbInfo.Height)) 713 } 714 715 /*if !tc.skipPayloadValidation { 716 compare := func(get func(string) ([]byte, *model.Response), name string) { 717 data, resp := get(ri.Id) 718 if resp.Error != nil { 719 t.Fatal(resp.Error) 720 } 721 722 expected, err := ioutil.ReadFile(filepath.Join(testDir, name)) 723 if err != nil { 724 t.Fatal(err) 725 } 726 727 if bytes.Compare(data, expected) != 0 { 728 tf, err := ioutil.TempFile("", fmt.Sprintf("test_%v_*_%s", i, name)) 729 if err != nil { 730 t.Fatal(err) 731 } 732 io.Copy(tf, bytes.NewReader(data)) 733 tf.Close() 734 t.Errorf("Actual data mismatched %s, written to %q", name, tf.Name()) 735 } 736 } 737 if len(tc.expectedPayloadNames) == 0 { 738 tc.expectedPayloadNames = tc.names 739 } 740 741 compare(client.GetFile, tc.expectedPayloadNames[i]) 742 if len(tc.expectedImageThumbnailNames) > i { 743 compare(client.GetFileThumbnail, tc.expectedImageThumbnailNames[i]) 744 } 745 if len(tc.expectedImageThumbnailNames) > i { 746 compare(client.GetFilePreview, tc.expectedImagePreviewNames[i]) 747 } 748 }*/ 749 750 th.cleanupTestFile(dbInfo) 751 } 752 }) 753 754 if restoreConfig != nil { 755 restoreConfig(th.App) 756 } 757 } 758 } 759 } 760 761 func TestGetFile(t *testing.T) { 762 th := Setup().InitBasic() 763 defer th.TearDown() 764 Client := th.Client 765 channel := th.BasicChannel 766 767 if *th.App.Config().FileSettings.DriverName == "" { 768 t.Skip("skipping because no file driver is enabled") 769 } 770 771 fileId := "" 772 var sent []byte 773 var err error 774 if sent, err = testutils.ReadTestFile("test.png"); err != nil { 775 t.Fatal(err) 776 } else { 777 fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png") 778 CheckNoError(t, resp) 779 780 fileId = fileResp.FileInfos[0].Id 781 } 782 783 data, resp := Client.GetFile(fileId) 784 CheckNoError(t, resp) 785 786 if len(data) == 0 { 787 t.Fatal("should not be empty") 788 } 789 790 for i := range data { 791 if data[i] != sent[i] { 792 t.Fatal("received file didn't match sent one") 793 } 794 } 795 796 _, resp = Client.GetFile("junk") 797 CheckBadRequestStatus(t, resp) 798 799 _, resp = Client.GetFile(model.NewId()) 800 CheckNotFoundStatus(t, resp) 801 802 Client.Logout() 803 _, resp = Client.GetFile(fileId) 804 CheckUnauthorizedStatus(t, resp) 805 806 _, resp = th.SystemAdminClient.GetFile(fileId) 807 CheckNoError(t, resp) 808 } 809 810 func TestGetFileHeaders(t *testing.T) { 811 th := Setup().InitBasic() 812 defer th.TearDown() 813 814 Client := th.Client 815 channel := th.BasicChannel 816 817 if *th.App.Config().FileSettings.DriverName == "" { 818 t.Skip("skipping because no file driver is enabled") 819 } 820 821 testHeaders := func(data []byte, filename string, expectedContentType string, getInline bool) func(*testing.T) { 822 return func(t *testing.T) { 823 fileResp, resp := Client.UploadFile(data, channel.Id, filename) 824 CheckNoError(t, resp) 825 826 fileId := fileResp.FileInfos[0].Id 827 828 _, resp = Client.GetFile(fileId) 829 CheckNoError(t, resp) 830 831 if contentType := resp.Header.Get("Content-Type"); !strings.HasPrefix(contentType, expectedContentType) { 832 t.Fatal("returned incorrect Content-Type", contentType) 833 } 834 835 if getInline { 836 if contentDisposition := resp.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "inline") { 837 t.Fatal("returned incorrect Content-Disposition", contentDisposition) 838 } 839 } else { 840 if contentDisposition := resp.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "attachment") { 841 t.Fatal("returned incorrect Content-Disposition", contentDisposition) 842 } 843 } 844 845 _, resp = Client.DownloadFile(fileId, true) 846 CheckNoError(t, resp) 847 848 if contentType := resp.Header.Get("Content-Type"); !strings.HasPrefix(contentType, expectedContentType) { 849 t.Fatal("returned incorrect Content-Type", contentType) 850 } 851 852 if contentDisposition := resp.Header.Get("Content-Disposition"); !strings.HasPrefix(contentDisposition, "attachment") { 853 t.Fatal("returned incorrect Content-Disposition", contentDisposition) 854 } 855 } 856 } 857 858 data := []byte("ABC") 859 860 t.Run("png", testHeaders(data, "test.png", "image/png", true)) 861 t.Run("gif", testHeaders(data, "test.gif", "image/gif", true)) 862 t.Run("mp4", testHeaders(data, "test.mp4", "video/mp4", true)) 863 t.Run("mp3", testHeaders(data, "test.mp3", "audio/mpeg", true)) 864 t.Run("pdf", testHeaders(data, "test.pdf", "application/pdf", false)) 865 t.Run("txt", testHeaders(data, "test.txt", "text/plain", false)) 866 t.Run("html", testHeaders(data, "test.html", "text/plain", false)) 867 t.Run("js", testHeaders(data, "test.js", "text/plain", false)) 868 t.Run("go", testHeaders(data, "test.go", "application/octet-stream", false)) 869 t.Run("zip", testHeaders(data, "test.zip", "application/zip", false)) 870 // Not every platform can recognize these 871 //t.Run("exe", testHeaders(data, "test.exe", "application/x-ms", false)) 872 t.Run("no extension", testHeaders(data, "test", "application/octet-stream", false)) 873 t.Run("no extension 2", testHeaders([]byte("<html></html>"), "test", "application/octet-stream", false)) 874 } 875 876 func TestGetFileThumbnail(t *testing.T) { 877 th := Setup().InitBasic() 878 defer th.TearDown() 879 Client := th.Client 880 channel := th.BasicChannel 881 882 if *th.App.Config().FileSettings.DriverName == "" { 883 t.Skip("skipping because no file driver is enabled") 884 } 885 886 fileId := "" 887 var sent []byte 888 var err error 889 if sent, err = testutils.ReadTestFile("test.png"); err != nil { 890 t.Fatal(err) 891 } else { 892 fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png") 893 CheckNoError(t, resp) 894 895 fileId = fileResp.FileInfos[0].Id 896 } 897 898 // Wait a bit for files to ready 899 time.Sleep(2 * time.Second) 900 901 data, resp := Client.GetFileThumbnail(fileId) 902 CheckNoError(t, resp) 903 904 if len(data) == 0 { 905 t.Fatal("should not be empty") 906 } 907 908 _, resp = Client.GetFileThumbnail("junk") 909 CheckBadRequestStatus(t, resp) 910 911 _, resp = Client.GetFileThumbnail(model.NewId()) 912 CheckNotFoundStatus(t, resp) 913 914 Client.Logout() 915 _, resp = Client.GetFileThumbnail(fileId) 916 CheckUnauthorizedStatus(t, resp) 917 918 otherUser := th.CreateUser() 919 Client.Login(otherUser.Email, otherUser.Password) 920 _, resp = Client.GetFileThumbnail(fileId) 921 CheckForbiddenStatus(t, resp) 922 923 Client.Logout() 924 _, resp = th.SystemAdminClient.GetFileThumbnail(fileId) 925 CheckNoError(t, resp) 926 } 927 928 func TestGetFileLink(t *testing.T) { 929 th := Setup().InitBasic() 930 defer th.TearDown() 931 Client := th.Client 932 channel := th.BasicChannel 933 934 if *th.App.Config().FileSettings.DriverName == "" { 935 t.Skip("skipping because no file driver is enabled") 936 } 937 938 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true }) 939 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) }) 940 941 fileId := "" 942 if data, err := testutils.ReadTestFile("test.png"); err != nil { 943 t.Fatal(err) 944 } else { 945 fileResp, resp := Client.UploadFile(data, channel.Id, "test.png") 946 CheckNoError(t, resp) 947 948 fileId = fileResp.FileInfos[0].Id 949 } 950 951 _, resp := Client.GetFileLink(fileId) 952 CheckBadRequestStatus(t, resp) 953 954 // Hacky way to assign file to a post (usually would be done by CreatePost call) 955 err := th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id, th.BasicUser.Id) 956 require.Nil(t, err) 957 958 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = false }) 959 _, resp = Client.GetFileLink(fileId) 960 CheckNotImplementedStatus(t, resp) 961 962 // Wait a bit for files to ready 963 time.Sleep(2 * time.Second) 964 965 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true }) 966 link, resp := Client.GetFileLink(fileId) 967 CheckNoError(t, resp) 968 969 if link == "" { 970 t.Fatal("should've received public link") 971 } 972 973 _, resp = Client.GetFileLink("junk") 974 CheckBadRequestStatus(t, resp) 975 976 _, resp = Client.GetFileLink(model.NewId()) 977 CheckNotFoundStatus(t, resp) 978 979 Client.Logout() 980 _, resp = Client.GetFileLink(fileId) 981 CheckUnauthorizedStatus(t, resp) 982 983 otherUser := th.CreateUser() 984 Client.Login(otherUser.Email, otherUser.Password) 985 _, resp = Client.GetFileLink(fileId) 986 CheckForbiddenStatus(t, resp) 987 988 Client.Logout() 989 _, resp = th.SystemAdminClient.GetFileLink(fileId) 990 CheckNoError(t, resp) 991 992 fileInfo, err := th.App.Srv.Store.FileInfo().Get(fileId) 993 require.Nil(t, err) 994 th.cleanupTestFile(fileInfo) 995 } 996 997 func TestGetFilePreview(t *testing.T) { 998 th := Setup().InitBasic() 999 defer th.TearDown() 1000 Client := th.Client 1001 channel := th.BasicChannel 1002 1003 if *th.App.Config().FileSettings.DriverName == "" { 1004 t.Skip("skipping because no file driver is enabled") 1005 } 1006 1007 fileId := "" 1008 var sent []byte 1009 var err error 1010 if sent, err = testutils.ReadTestFile("test.png"); err != nil { 1011 t.Fatal(err) 1012 } else { 1013 fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png") 1014 CheckNoError(t, resp) 1015 1016 fileId = fileResp.FileInfos[0].Id 1017 } 1018 1019 // Wait a bit for files to ready 1020 time.Sleep(2 * time.Second) 1021 1022 data, resp := Client.GetFilePreview(fileId) 1023 CheckNoError(t, resp) 1024 1025 if len(data) == 0 { 1026 t.Fatal("should not be empty") 1027 } 1028 1029 _, resp = Client.GetFilePreview("junk") 1030 CheckBadRequestStatus(t, resp) 1031 1032 _, resp = Client.GetFilePreview(model.NewId()) 1033 CheckNotFoundStatus(t, resp) 1034 1035 Client.Logout() 1036 _, resp = Client.GetFilePreview(fileId) 1037 CheckUnauthorizedStatus(t, resp) 1038 1039 otherUser := th.CreateUser() 1040 Client.Login(otherUser.Email, otherUser.Password) 1041 _, resp = Client.GetFilePreview(fileId) 1042 CheckForbiddenStatus(t, resp) 1043 1044 Client.Logout() 1045 _, resp = th.SystemAdminClient.GetFilePreview(fileId) 1046 CheckNoError(t, resp) 1047 } 1048 1049 func TestGetFileInfo(t *testing.T) { 1050 th := Setup().InitBasic() 1051 defer th.TearDown() 1052 Client := th.Client 1053 user := th.BasicUser 1054 channel := th.BasicChannel 1055 1056 if *th.App.Config().FileSettings.DriverName == "" { 1057 t.Skip("skipping because no file driver is enabled") 1058 } 1059 1060 fileId := "" 1061 var sent []byte 1062 var err error 1063 if sent, err = testutils.ReadTestFile("test.png"); err != nil { 1064 t.Fatal(err) 1065 } else { 1066 fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png") 1067 CheckNoError(t, resp) 1068 1069 fileId = fileResp.FileInfos[0].Id 1070 } 1071 1072 // Wait a bit for files to ready 1073 time.Sleep(2 * time.Second) 1074 1075 info, resp := Client.GetFileInfo(fileId) 1076 CheckNoError(t, resp) 1077 1078 if err != nil { 1079 t.Fatal(err) 1080 } else if info.Id != fileId { 1081 t.Fatal("got incorrect file") 1082 } else if info.CreatorId != user.Id { 1083 t.Fatal("file should be assigned to user") 1084 } else if info.PostId != "" { 1085 t.Fatal("file shouldn't have a post") 1086 } else if info.Path != "" { 1087 t.Fatal("file path shouldn't have been returned to client") 1088 } else if info.ThumbnailPath != "" { 1089 t.Fatal("file thumbnail path shouldn't have been returned to client") 1090 } else if info.PreviewPath != "" { 1091 t.Fatal("file preview path shouldn't have been returned to client") 1092 } else if info.MimeType != "image/png" { 1093 t.Fatal("mime type should've been image/png") 1094 } 1095 1096 _, resp = Client.GetFileInfo("junk") 1097 CheckBadRequestStatus(t, resp) 1098 1099 _, resp = Client.GetFileInfo(model.NewId()) 1100 CheckNotFoundStatus(t, resp) 1101 1102 Client.Logout() 1103 _, resp = Client.GetFileInfo(fileId) 1104 CheckUnauthorizedStatus(t, resp) 1105 1106 otherUser := th.CreateUser() 1107 Client.Login(otherUser.Email, otherUser.Password) 1108 _, resp = Client.GetFileInfo(fileId) 1109 CheckForbiddenStatus(t, resp) 1110 1111 Client.Logout() 1112 _, resp = th.SystemAdminClient.GetFileInfo(fileId) 1113 CheckNoError(t, resp) 1114 } 1115 1116 func TestGetPublicFile(t *testing.T) { 1117 th := Setup().InitBasic() 1118 defer th.TearDown() 1119 Client := th.Client 1120 channel := th.BasicChannel 1121 1122 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true }) 1123 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) }) 1124 1125 fileId := "" 1126 if data, err := testutils.ReadTestFile("test.png"); err != nil { 1127 t.Fatal(err) 1128 } else { 1129 fileResp, resp := Client.UploadFile(data, channel.Id, "test.png") 1130 CheckNoError(t, resp) 1131 1132 fileId = fileResp.FileInfos[0].Id 1133 } 1134 1135 // Hacky way to assign file to a post (usually would be done by CreatePost call) 1136 err := th.App.Srv.Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id, th.BasicUser.Id) 1137 require.Nil(t, err) 1138 1139 info, err := th.App.Srv.Store.FileInfo().Get(fileId) 1140 require.Nil(t, err) 1141 link := th.App.GeneratePublicLink(Client.Url, info) 1142 1143 // Wait a bit for files to ready 1144 time.Sleep(2 * time.Second) 1145 1146 if resp, err := http.Get(link); err != nil || resp.StatusCode != http.StatusOK { 1147 t.Log(link) 1148 t.Fatal("failed to get image with public link", err) 1149 } 1150 1151 if resp, err := http.Get(link[:strings.LastIndex(link, "?")]); err == nil && resp.StatusCode != http.StatusBadRequest { 1152 t.Fatal("should've failed to get image with public link without hash", resp.Status) 1153 } 1154 1155 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = false }) 1156 if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusNotImplemented { 1157 t.Fatal("should've failed to get image with disabled public link") 1158 } 1159 1160 // test after the salt has changed 1161 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true }) 1162 th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) }) 1163 1164 if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest { 1165 t.Fatal("should've failed to get image with public link after salt changed") 1166 } 1167 1168 if resp, err := http.Get(link); err == nil && resp.StatusCode != http.StatusBadRequest { 1169 t.Fatal("should've failed to get image with public link after salt changed") 1170 } 1171 1172 fileInfo, err := th.App.Srv.Store.FileInfo().Get(fileId) 1173 require.Nil(t, err) 1174 require.Nil(t, th.cleanupTestFile(fileInfo)) 1175 th.cleanupTestFile(info) 1176 }