file_test.go 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985
  1. // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
  2. // See LICENSE.txt for license information.
  3. package api4
  4. import (
  5. "bytes"
  6. "crypto/rand"
  7. "fmt"
  8. "io"
  9. "io/ioutil"
  10. "mime/multipart"
  11. "net/http"
  12. "net/textproto"
  13. "net/url"
  14. "os"
  15. "path/filepath"
  16. "strings"
  17. "testing"
  18. "time"
  19. "github.com/mattermost/mattermost-server/v5/app"
  20. "github.com/mattermost/mattermost-server/v5/model"
  21. "github.com/mattermost/mattermost-server/v5/utils/fileutils"
  22. "github.com/mattermost/mattermost-server/v5/utils/testutils"
  23. "github.com/stretchr/testify/assert"
  24. "github.com/stretchr/testify/require"
  25. )
  26. var testDir = ""
  27. func init() {
  28. testDir, _ = fileutils.FindDir("tests")
  29. }
  30. // File Section
  31. var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
  32. func escapeQuotes(s string) string {
  33. return quoteEscaper.Replace(s)
  34. }
  35. func randomBytes(t *testing.T, n int) []byte {
  36. bb := make([]byte, n)
  37. _, err := rand.Read(bb)
  38. require.NoError(t, err)
  39. return bb
  40. }
  41. func fileBytes(t *testing.T, path string) []byte {
  42. path = filepath.Join(testDir, path)
  43. f, err := os.Open(path)
  44. require.NoError(t, err)
  45. defer f.Close()
  46. bb, err := ioutil.ReadAll(f)
  47. require.NoError(t, err)
  48. return bb
  49. }
  50. func testDoUploadFileRequest(t testing.TB, c *model.Client4, url string, blob []byte, contentType string,
  51. contentLength int64) (*model.FileUploadResponse, *model.Response) {
  52. req, err := http.NewRequest("POST", c.ApiUrl+c.GetFilesRoute()+url, bytes.NewReader(blob))
  53. require.Nil(t, err)
  54. if contentLength != 0 {
  55. req.ContentLength = contentLength
  56. }
  57. req.Header.Set("Content-Type", contentType)
  58. if len(c.AuthToken) > 0 {
  59. req.Header.Set(model.HEADER_AUTH, c.AuthType+" "+c.AuthToken)
  60. }
  61. resp, err := c.HttpClient.Do(req)
  62. require.Nil(t, err)
  63. require.NotNil(t, resp)
  64. defer closeBody(resp)
  65. if resp.StatusCode >= 300 {
  66. return nil, model.BuildErrorResponse(resp, model.AppErrorFromJson(resp.Body))
  67. }
  68. return model.FileUploadResponseFromJson(resp.Body), model.BuildResponse(resp)
  69. }
  70. func testUploadFilesPost(
  71. t testing.TB,
  72. c *model.Client4,
  73. channelId string,
  74. names []string,
  75. blobs [][]byte,
  76. clientIds []string,
  77. useChunked bool,
  78. ) (*model.FileUploadResponse, *model.Response) {
  79. // Do not check len(clientIds), leave it entirely to the user to
  80. // provide. The server will error out if it does not match the number
  81. // of files, but it's not critical here.
  82. require.NotEmpty(t, names)
  83. require.NotEmpty(t, blobs)
  84. require.Equal(t, len(names), len(blobs))
  85. fileUploadResponse := &model.FileUploadResponse{}
  86. for i, blob := range blobs {
  87. var cl int64
  88. if useChunked {
  89. cl = -1
  90. } else {
  91. cl = int64(len(blob))
  92. }
  93. ct := http.DetectContentType(blob)
  94. postURL := fmt.Sprintf("?channel_id=%v", url.QueryEscape(channelId)) +
  95. fmt.Sprintf("&filename=%v", url.QueryEscape(names[i]))
  96. if len(clientIds) > i {
  97. postURL += fmt.Sprintf("&client_id=%v", url.QueryEscape(clientIds[i]))
  98. }
  99. fur, resp := testDoUploadFileRequest(t, c, postURL, blob, ct, cl)
  100. if resp.Error != nil {
  101. return nil, resp
  102. }
  103. fileUploadResponse.FileInfos = append(fileUploadResponse.FileInfos, fur.FileInfos[0])
  104. if len(clientIds) > 0 {
  105. if len(fur.ClientIds) > 0 {
  106. fileUploadResponse.ClientIds = append(fileUploadResponse.ClientIds, fur.ClientIds[0])
  107. } else {
  108. fileUploadResponse.ClientIds = append(fileUploadResponse.ClientIds, "")
  109. }
  110. }
  111. }
  112. return fileUploadResponse, nil
  113. }
  114. func testUploadFilesMultipart(
  115. t testing.TB,
  116. c *model.Client4,
  117. channelId string,
  118. names []string,
  119. blobs [][]byte,
  120. clientIds []string,
  121. ) (
  122. fileUploadResponse *model.FileUploadResponse,
  123. response *model.Response,
  124. ) {
  125. // Do not check len(clientIds), leave it entirely to the user to
  126. // provide. The server will error out if it does not match the number
  127. // of files, but it's not critical here.
  128. require.NotEmpty(t, names)
  129. require.NotEmpty(t, blobs)
  130. require.Equal(t, len(names), len(blobs))
  131. mwBody := &bytes.Buffer{}
  132. mw := multipart.NewWriter(mwBody)
  133. err := mw.WriteField("channel_id", channelId)
  134. require.Nil(t, err)
  135. for i, blob := range blobs {
  136. ct := http.DetectContentType(blob)
  137. if len(clientIds) > i {
  138. err = mw.WriteField("client_ids", clientIds[i])
  139. require.Nil(t, err)
  140. }
  141. h := textproto.MIMEHeader{}
  142. h.Set("Content-Disposition",
  143. fmt.Sprintf(`form-data; name="files"; filename="%s"`, escapeQuotes(names[i])))
  144. h.Set("Content-Type", ct)
  145. // If we error here, writing to mw, the deferred handler
  146. part, err := mw.CreatePart(h)
  147. require.Nil(t, err)
  148. _, err = io.Copy(part, bytes.NewReader(blob))
  149. require.Nil(t, err)
  150. }
  151. require.NoError(t, mw.Close())
  152. return testDoUploadFileRequest(t, c, "", mwBody.Bytes(), mw.FormDataContentType(), -1)
  153. }
  154. func TestUploadFiles(t *testing.T) {
  155. th := Setup(t).InitBasic()
  156. defer th.TearDown()
  157. if *th.App.Config().FileSettings.DriverName == "" {
  158. t.Skip("skipping because no file driver is enabled")
  159. }
  160. channel := th.BasicChannel
  161. date := time.Now().Format("20060102")
  162. // Get better error messages
  163. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.ServiceSettings.EnableDeveloper = true })
  164. tests := []struct {
  165. title string
  166. client *model.Client4
  167. blobs [][]byte
  168. names []string
  169. clientIds []string
  170. skipSuccessValidation bool
  171. skipPayloadValidation bool
  172. skipSimplePost bool
  173. skipMultipart bool
  174. channelId string
  175. useChunkedInSimplePost bool
  176. expectedCreatorId string
  177. expectedPayloadNames []string
  178. expectImage bool
  179. expectedImageWidths []int
  180. expectedImageHeights []int
  181. expectedImageThumbnailNames []string
  182. expectedImagePreviewNames []string
  183. expectedImageHasPreview []bool
  184. setupConfig func(a *app.App) func(a *app.App)
  185. checkResponse func(t *testing.T, resp *model.Response)
  186. }{
  187. // Upload a bunch of files, mixed images and non-images
  188. {
  189. title: "Happy",
  190. names: []string{"test.png", "testgif.gif", "testplugin.tar.gz", "test-search.md", "test.tiff"},
  191. expectedCreatorId: th.BasicUser.Id,
  192. },
  193. // Upload a bunch of files, with clientIds
  194. {
  195. title: "Happy client_ids",
  196. names: []string{"test.png", "testgif.gif", "testplugin.tar.gz", "test-search.md", "test.tiff"},
  197. clientIds: []string{"1", "2", "3", "4", "5"},
  198. expectedCreatorId: th.BasicUser.Id,
  199. },
  200. // Upload a bunch of images. testgif.gif is an animated GIF,
  201. // so it does not have HasPreviewImage set.
  202. {
  203. title: "Happy images",
  204. names: []string{"test.png", "testgif.gif"},
  205. expectImage: true,
  206. expectedCreatorId: th.BasicUser.Id,
  207. expectedImageWidths: []int{408, 118},
  208. expectedImageHeights: []int{336, 118},
  209. expectedImageHasPreview: []bool{true, false},
  210. },
  211. {
  212. title: "Happy invalid image",
  213. names: []string{"testgif.gif"},
  214. blobs: [][]byte{fileBytes(t, "test-search.md")},
  215. skipPayloadValidation: true,
  216. expectedCreatorId: th.BasicUser.Id,
  217. },
  218. // Simple POST, chunked encoding
  219. {
  220. title: "Happy image chunked post",
  221. skipMultipart: true,
  222. useChunkedInSimplePost: true,
  223. names: []string{"test.png"},
  224. expectImage: true,
  225. expectedImageWidths: []int{408},
  226. expectedImageHeights: []int{336},
  227. expectedImageHasPreview: []bool{true},
  228. expectedCreatorId: th.BasicUser.Id,
  229. },
  230. // Image thumbnail and preview: size and orientation. Note that
  231. // the expected image dimensions remain the same regardless of the
  232. // orientation - what we save in FileInfo is used by the
  233. // clients to size UI elements, so the dimensions are "actual".
  234. {
  235. title: "Happy image thumbnail/preview 1",
  236. names: []string{"orientation_test_1.jpeg"},
  237. expectedImageThumbnailNames: []string{"orientation_test_1_expected_thumb.jpeg"},
  238. expectedImagePreviewNames: []string{"orientation_test_1_expected_preview.jpeg"},
  239. expectImage: true,
  240. expectedImageWidths: []int{2860},
  241. expectedImageHeights: []int{1578},
  242. expectedImageHasPreview: []bool{true},
  243. expectedCreatorId: th.BasicUser.Id,
  244. },
  245. {
  246. title: "Happy image thumbnail/preview 2",
  247. names: []string{"orientation_test_2.jpeg"},
  248. expectedImageThumbnailNames: []string{"orientation_test_2_expected_thumb.jpeg"},
  249. expectedImagePreviewNames: []string{"orientation_test_2_expected_preview.jpeg"},
  250. expectImage: true,
  251. expectedImageWidths: []int{2860},
  252. expectedImageHeights: []int{1578},
  253. expectedImageHasPreview: []bool{true},
  254. expectedCreatorId: th.BasicUser.Id,
  255. },
  256. {
  257. title: "Happy image thumbnail/preview 3",
  258. names: []string{"orientation_test_3.jpeg"},
  259. expectedImageThumbnailNames: []string{"orientation_test_3_expected_thumb.jpeg"},
  260. expectedImagePreviewNames: []string{"orientation_test_3_expected_preview.jpeg"},
  261. expectImage: true,
  262. expectedImageWidths: []int{2860},
  263. expectedImageHeights: []int{1578},
  264. expectedImageHasPreview: []bool{true},
  265. expectedCreatorId: th.BasicUser.Id,
  266. },
  267. {
  268. title: "Happy image thumbnail/preview 4",
  269. names: []string{"orientation_test_4.jpeg"},
  270. expectedImageThumbnailNames: []string{"orientation_test_4_expected_thumb.jpeg"},
  271. expectedImagePreviewNames: []string{"orientation_test_4_expected_preview.jpeg"},
  272. expectImage: true,
  273. expectedImageWidths: []int{2860},
  274. expectedImageHeights: []int{1578},
  275. expectedImageHasPreview: []bool{true},
  276. expectedCreatorId: th.BasicUser.Id,
  277. },
  278. {
  279. title: "Happy image thumbnail/preview 5",
  280. names: []string{"orientation_test_5.jpeg"},
  281. expectedImageThumbnailNames: []string{"orientation_test_5_expected_thumb.jpeg"},
  282. expectedImagePreviewNames: []string{"orientation_test_5_expected_preview.jpeg"},
  283. expectImage: true,
  284. expectedImageWidths: []int{2860},
  285. expectedImageHeights: []int{1578},
  286. expectedImageHasPreview: []bool{true},
  287. expectedCreatorId: th.BasicUser.Id,
  288. },
  289. {
  290. title: "Happy image thumbnail/preview 6",
  291. names: []string{"orientation_test_6.jpeg"},
  292. expectedImageThumbnailNames: []string{"orientation_test_6_expected_thumb.jpeg"},
  293. expectedImagePreviewNames: []string{"orientation_test_6_expected_preview.jpeg"},
  294. expectImage: true,
  295. expectedImageWidths: []int{2860},
  296. expectedImageHeights: []int{1578},
  297. expectedImageHasPreview: []bool{true},
  298. expectedCreatorId: th.BasicUser.Id,
  299. },
  300. {
  301. title: "Happy image thumbnail/preview 7",
  302. names: []string{"orientation_test_7.jpeg"},
  303. expectedImageThumbnailNames: []string{"orientation_test_7_expected_thumb.jpeg"},
  304. expectedImagePreviewNames: []string{"orientation_test_7_expected_preview.jpeg"},
  305. expectImage: true,
  306. expectedImageWidths: []int{2860},
  307. expectedImageHeights: []int{1578},
  308. expectedImageHasPreview: []bool{true},
  309. expectedCreatorId: th.BasicUser.Id,
  310. },
  311. {
  312. title: "Happy image thumbnail/preview 8",
  313. names: []string{"orientation_test_8.jpeg"},
  314. expectedImageThumbnailNames: []string{"orientation_test_8_expected_thumb.jpeg"},
  315. expectedImagePreviewNames: []string{"orientation_test_8_expected_preview.jpeg"},
  316. expectImage: true,
  317. expectedImageWidths: []int{2860},
  318. expectedImageHeights: []int{1578},
  319. expectedImageHasPreview: []bool{true},
  320. expectedCreatorId: th.BasicUser.Id,
  321. },
  322. // TIFF preview test
  323. {
  324. title: "Happy image thumbnail/preview 9",
  325. names: []string{"test.tiff"},
  326. expectedImageThumbnailNames: []string{"test_expected_tiff_thumb.jpeg"},
  327. expectedImagePreviewNames: []string{"test_expected_tiff_preview.jpeg"},
  328. expectImage: true,
  329. expectedImageWidths: []int{701},
  330. expectedImageHeights: []int{701},
  331. expectedImageHasPreview: []bool{true},
  332. expectedCreatorId: th.BasicUser.Id,
  333. },
  334. {
  335. title: "Happy admin",
  336. client: th.SystemAdminClient,
  337. names: []string{"test.png"},
  338. expectedCreatorId: th.SystemAdminUser.Id,
  339. },
  340. {
  341. title: "Happy stream",
  342. useChunkedInSimplePost: true,
  343. skipPayloadValidation: true,
  344. names: []string{"1Mb-stream"},
  345. blobs: [][]byte{randomBytes(t, 1024*1024)},
  346. setupConfig: func(a *app.App) func(a *app.App) {
  347. maxFileSize := *a.Config().FileSettings.MaxFileSize
  348. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 1024 * 1024 })
  349. return func(a *app.App) {
  350. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize })
  351. }
  352. },
  353. expectedCreatorId: th.BasicUser.Id,
  354. },
  355. // Error cases
  356. {
  357. title: "Error channel_id does not exist",
  358. channelId: model.NewId(),
  359. names: []string{"test.png"},
  360. skipSuccessValidation: true,
  361. checkResponse: CheckForbiddenStatus,
  362. },
  363. {
  364. // on simple post this uploads the last file
  365. // successfully, without a ClientId
  366. title: "Error too few client_ids",
  367. skipSimplePost: true,
  368. names: []string{"test.png", "testplugin.tar.gz", "test-search.md"},
  369. clientIds: []string{"1", "4"},
  370. skipSuccessValidation: true,
  371. checkResponse: CheckBadRequestStatus,
  372. },
  373. {
  374. title: "Error invalid channel_id",
  375. channelId: "../../junk",
  376. names: []string{"test.png"},
  377. skipSuccessValidation: true,
  378. checkResponse: CheckBadRequestStatus,
  379. },
  380. {
  381. title: "Error admin channel_id does not exist",
  382. client: th.SystemAdminClient,
  383. channelId: model.NewId(),
  384. names: []string{"test.png"},
  385. skipSuccessValidation: true,
  386. checkResponse: CheckForbiddenStatus,
  387. },
  388. {
  389. title: "Error admin invalid channel_id",
  390. client: th.SystemAdminClient,
  391. channelId: "../../junk",
  392. names: []string{"test.png"},
  393. skipSuccessValidation: true,
  394. checkResponse: CheckBadRequestStatus,
  395. },
  396. {
  397. title: "Error admin disabled uploads",
  398. client: th.SystemAdminClient,
  399. names: []string{"test.png"},
  400. skipSuccessValidation: true,
  401. checkResponse: CheckNotImplementedStatus,
  402. setupConfig: func(a *app.App) func(a *app.App) {
  403. enableFileAttachments := *a.Config().FileSettings.EnableFileAttachments
  404. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnableFileAttachments = false })
  405. return func(a *app.App) {
  406. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnableFileAttachments = enableFileAttachments })
  407. }
  408. },
  409. },
  410. {
  411. title: "Error file too large",
  412. names: []string{"test.png"},
  413. skipSuccessValidation: true,
  414. checkResponse: CheckRequestEntityTooLargeStatus,
  415. setupConfig: func(a *app.App) func(a *app.App) {
  416. maxFileSize := *a.Config().FileSettings.MaxFileSize
  417. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 279590 })
  418. return func(a *app.App) {
  419. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize })
  420. }
  421. },
  422. },
  423. // File too large (chunked, simple POST only, multipart would've been redundant with above)
  424. {
  425. title: "File too large chunked",
  426. useChunkedInSimplePost: true,
  427. skipMultipart: true,
  428. names: []string{"test.png"},
  429. skipSuccessValidation: true,
  430. checkResponse: CheckRequestEntityTooLargeStatus,
  431. setupConfig: func(a *app.App) func(a *app.App) {
  432. maxFileSize := *a.Config().FileSettings.MaxFileSize
  433. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 279590 })
  434. return func(a *app.App) {
  435. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize })
  436. }
  437. },
  438. },
  439. {
  440. title: "Error stream too large",
  441. skipPayloadValidation: true,
  442. names: []string{"1Mb-stream"},
  443. blobs: [][]byte{randomBytes(t, 1024*1024)},
  444. skipSuccessValidation: true,
  445. checkResponse: CheckRequestEntityTooLargeStatus,
  446. setupConfig: func(a *app.App) func(a *app.App) {
  447. maxFileSize := *a.Config().FileSettings.MaxFileSize
  448. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = 10 * 1024 })
  449. return func(a *app.App) {
  450. a.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.MaxFileSize = maxFileSize })
  451. }
  452. },
  453. },
  454. }
  455. for _, useMultipart := range []bool{true, false} {
  456. for _, tc := range tests {
  457. if tc.skipMultipart && useMultipart || tc.skipSimplePost && !useMultipart {
  458. continue
  459. }
  460. title := ""
  461. if useMultipart {
  462. title = "multipart "
  463. } else {
  464. title = "simple "
  465. }
  466. if tc.title != "" {
  467. title += tc.title + " "
  468. }
  469. title += fmt.Sprintf("%v", tc.names)
  470. t.Run(title, func(t *testing.T) {
  471. // Apply any necessary config changes
  472. if tc.setupConfig != nil {
  473. restoreConfig := tc.setupConfig(th.App)
  474. if restoreConfig != nil {
  475. defer restoreConfig(th.App)
  476. }
  477. }
  478. // Set the default values
  479. client := th.Client
  480. if tc.client != nil {
  481. client = tc.client
  482. }
  483. channelId := channel.Id
  484. if tc.channelId != "" {
  485. channelId = tc.channelId
  486. }
  487. blobs := tc.blobs
  488. if len(blobs) == 0 {
  489. for _, name := range tc.names {
  490. blobs = append(blobs, fileBytes(t, name))
  491. }
  492. }
  493. var fileResp *model.FileUploadResponse
  494. var resp *model.Response
  495. if useMultipart {
  496. fileResp, resp = testUploadFilesMultipart(t, client, channelId, tc.names, blobs, tc.clientIds)
  497. } else {
  498. fileResp, resp = testUploadFilesPost(t, client, channelId, tc.names, blobs, tc.clientIds, tc.useChunkedInSimplePost)
  499. }
  500. if tc.checkResponse != nil {
  501. tc.checkResponse(t, resp)
  502. } else {
  503. if resp != nil {
  504. require.Nil(t, resp.Error)
  505. }
  506. }
  507. if tc.skipSuccessValidation {
  508. return
  509. }
  510. require.NotNil(t, fileResp, "Nil fileResp")
  511. require.NotEqual(t, 0, len(fileResp.FileInfos), "Empty FileInfos")
  512. require.Equal(t, len(tc.names), len(fileResp.FileInfos), "Mismatched actual or expected FileInfos")
  513. for i, ri := range fileResp.FileInfos {
  514. // The returned file info from the upload call will be missing some fields that will be stored in the database
  515. assert.Equal(t, ri.CreatorId, tc.expectedCreatorId, "File should be assigned to user")
  516. assert.Equal(t, ri.PostId, "", "File shouldn't have a post Id")
  517. assert.Equal(t, ri.Path, "", "File path should not be set on returned info")
  518. assert.Equal(t, ri.ThumbnailPath, "", "File thumbnail path should not be set on returned info")
  519. assert.Equal(t, ri.PreviewPath, "", "File preview path should not be set on returned info")
  520. if len(tc.clientIds) > i {
  521. assert.True(t, len(fileResp.ClientIds) == len(tc.clientIds),
  522. fmt.Sprintf("Wrong number of clientIds returned, expected %v, got %v", len(tc.clientIds), len(fileResp.ClientIds)))
  523. assert.Equal(t, fileResp.ClientIds[i], tc.clientIds[i],
  524. fmt.Sprintf("Wrong clientId returned, expected %v, got %v", tc.clientIds[i], fileResp.ClientIds[i]))
  525. }
  526. dbInfo, err := th.App.Srv().Store.FileInfo().Get(ri.Id)
  527. require.Nil(t, err)
  528. assert.Equal(t, dbInfo.Id, ri.Id, "File id from response should match one stored in database")
  529. assert.Equal(t, dbInfo.CreatorId, tc.expectedCreatorId, "F ile should be assigned to user")
  530. assert.Equal(t, dbInfo.PostId, "", "File shouldn't have a post")
  531. assert.NotEqual(t, dbInfo.Path, "", "File path should be set in database")
  532. _, fname := filepath.Split(dbInfo.Path)
  533. ext := filepath.Ext(fname)
  534. name := fname[:len(fname)-len(ext)]
  535. expectedDir := fmt.Sprintf("%v/teams/%v/channels/%v/users/%s/%s", date, FILE_TEAM_ID, channel.Id, ri.CreatorId, ri.Id)
  536. expectedPath := fmt.Sprintf("%s/%s", expectedDir, fname)
  537. assert.Equal(t, dbInfo.Path, expectedPath,
  538. fmt.Sprintf("File %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.Path, expectedPath))
  539. if tc.expectImage {
  540. expectedThumbnailPath := fmt.Sprintf("%s/%s_thumb.jpg", expectedDir, name)
  541. expectedPreviewPath := fmt.Sprintf("%s/%s_preview.jpg", expectedDir, name)
  542. assert.Equal(t, dbInfo.ThumbnailPath, expectedThumbnailPath,
  543. fmt.Sprintf("Thumbnail for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.ThumbnailPath, expectedThumbnailPath))
  544. assert.Equal(t, dbInfo.PreviewPath, expectedPreviewPath,
  545. fmt.Sprintf("Preview for %v saved to:%q, expected:%q", dbInfo.Name, dbInfo.PreviewPath, expectedPreviewPath))
  546. assert.True(t,
  547. dbInfo.HasPreviewImage == tc.expectedImageHasPreview[i],
  548. fmt.Sprintf("Image: HasPreviewImage should be set for %s", dbInfo.Name))
  549. assert.True(t,
  550. dbInfo.Width == tc.expectedImageWidths[i] && dbInfo.Height == tc.expectedImageHeights[i],
  551. fmt.Sprintf("Image dimensions: expected %dwx%dh, got %vwx%dh",
  552. tc.expectedImageWidths[i], tc.expectedImageHeights[i],
  553. dbInfo.Width, dbInfo.Height))
  554. }
  555. if !tc.skipPayloadValidation {
  556. compare := func(get func(string) ([]byte, *model.Response), name string) {
  557. data, resp := get(ri.Id)
  558. require.NotNil(t, resp)
  559. require.Nil(t, resp.Error)
  560. expected, err := ioutil.ReadFile(filepath.Join(testDir, name))
  561. require.Nil(t, err)
  562. if !bytes.Equal(data, expected) {
  563. tf, err := ioutil.TempFile("", fmt.Sprintf("test_%v_*_%s", i, name))
  564. defer tf.Close()
  565. require.Nil(t, err)
  566. _, err = io.Copy(tf, bytes.NewReader(data))
  567. require.Nil(t, err)
  568. t.Errorf("Actual data mismatched %s, written to %q - expected %d bytes, got %d.", name, tf.Name(), len(expected), len(data))
  569. }
  570. }
  571. if len(tc.expectedPayloadNames) == 0 {
  572. tc.expectedPayloadNames = tc.names
  573. }
  574. compare(client.GetFile, tc.expectedPayloadNames[i])
  575. if len(tc.expectedImageThumbnailNames) > i {
  576. compare(client.GetFileThumbnail, tc.expectedImageThumbnailNames[i])
  577. }
  578. if len(tc.expectedImageThumbnailNames) > i {
  579. compare(client.GetFilePreview, tc.expectedImagePreviewNames[i])
  580. }
  581. }
  582. th.cleanupTestFile(dbInfo)
  583. }
  584. })
  585. }
  586. }
  587. }
  588. func TestGetFile(t *testing.T) {
  589. th := Setup(t).InitBasic()
  590. defer th.TearDown()
  591. Client := th.Client
  592. channel := th.BasicChannel
  593. if *th.App.Config().FileSettings.DriverName == "" {
  594. t.Skip("skipping because no file driver is enabled")
  595. }
  596. sent, err := testutils.ReadTestFile("test.png")
  597. require.NoError(t, err)
  598. fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
  599. CheckNoError(t, resp)
  600. fileId := fileResp.FileInfos[0].Id
  601. data, resp := Client.GetFile(fileId)
  602. CheckNoError(t, resp)
  603. require.NotEqual(t, 0, len(data), "should not be empty")
  604. for i := range data {
  605. require.Equal(t, sent[i], data[i], "received file didn't match sent one")
  606. }
  607. _, resp = Client.GetFile("junk")
  608. CheckBadRequestStatus(t, resp)
  609. _, resp = Client.GetFile(model.NewId())
  610. CheckNotFoundStatus(t, resp)
  611. Client.Logout()
  612. _, resp = Client.GetFile(fileId)
  613. CheckUnauthorizedStatus(t, resp)
  614. _, resp = th.SystemAdminClient.GetFile(fileId)
  615. CheckNoError(t, resp)
  616. }
  617. func TestGetFileHeaders(t *testing.T) {
  618. th := Setup(t).InitBasic()
  619. defer th.TearDown()
  620. Client := th.Client
  621. channel := th.BasicChannel
  622. if *th.App.Config().FileSettings.DriverName == "" {
  623. t.Skip("skipping because no file driver is enabled")
  624. }
  625. testHeaders := func(data []byte, filename string, expectedContentType string, getInline bool) func(*testing.T) {
  626. return func(t *testing.T) {
  627. fileResp, resp := Client.UploadFile(data, channel.Id, filename)
  628. CheckNoError(t, resp)
  629. fileId := fileResp.FileInfos[0].Id
  630. _, resp = Client.GetFile(fileId)
  631. CheckNoError(t, resp)
  632. CheckStartsWith(t, resp.Header.Get("Content-Type"), expectedContentType, "returned incorrect Content-Type")
  633. if getInline {
  634. CheckStartsWith(t, resp.Header.Get("Content-Disposition"), "inline", "returned incorrect Content-Disposition")
  635. } else {
  636. CheckStartsWith(t, resp.Header.Get("Content-Disposition"), "attachment", "returned incorrect Content-Disposition")
  637. }
  638. _, resp = Client.DownloadFile(fileId, true)
  639. CheckNoError(t, resp)
  640. CheckStartsWith(t, resp.Header.Get("Content-Type"), expectedContentType, "returned incorrect Content-Type")
  641. CheckStartsWith(t, resp.Header.Get("Content-Disposition"), "attachment", "returned incorrect Content-Disposition")
  642. }
  643. }
  644. data := []byte("ABC")
  645. t.Run("png", testHeaders(data, "test.png", "image/png", true))
  646. t.Run("gif", testHeaders(data, "test.gif", "image/gif", true))
  647. t.Run("mp4", testHeaders(data, "test.mp4", "video/mp4", true))
  648. t.Run("mp3", testHeaders(data, "test.mp3", "audio/mpeg", true))
  649. t.Run("pdf", testHeaders(data, "test.pdf", "application/pdf", false))
  650. t.Run("txt", testHeaders(data, "test.txt", "text/plain", false))
  651. t.Run("html", testHeaders(data, "test.html", "text/plain", false))
  652. t.Run("js", testHeaders(data, "test.js", "text/plain", false))
  653. t.Run("go", testHeaders(data, "test.go", "application/octet-stream", false))
  654. t.Run("zip", testHeaders(data, "test.zip", "application/zip", false))
  655. // Not every platform can recognize these
  656. //t.Run("exe", testHeaders(data, "test.exe", "application/x-ms", false))
  657. t.Run("no extension", testHeaders(data, "test", "application/octet-stream", false))
  658. t.Run("no extension 2", testHeaders([]byte("<html></html>"), "test", "application/octet-stream", false))
  659. }
  660. func TestGetFileThumbnail(t *testing.T) {
  661. th := Setup(t).InitBasic()
  662. defer th.TearDown()
  663. Client := th.Client
  664. channel := th.BasicChannel
  665. if *th.App.Config().FileSettings.DriverName == "" {
  666. t.Skip("skipping because no file driver is enabled")
  667. }
  668. sent, err := testutils.ReadTestFile("test.png")
  669. require.NoError(t, err)
  670. fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
  671. CheckNoError(t, resp)
  672. fileId := fileResp.FileInfos[0].Id
  673. data, resp := Client.GetFileThumbnail(fileId)
  674. CheckNoError(t, resp)
  675. require.NotEqual(t, 0, len(data), "should not be empty")
  676. _, resp = Client.GetFileThumbnail("junk")
  677. CheckBadRequestStatus(t, resp)
  678. _, resp = Client.GetFileThumbnail(model.NewId())
  679. CheckNotFoundStatus(t, resp)
  680. Client.Logout()
  681. _, resp = Client.GetFileThumbnail(fileId)
  682. CheckUnauthorizedStatus(t, resp)
  683. otherUser := th.CreateUser()
  684. Client.Login(otherUser.Email, otherUser.Password)
  685. _, resp = Client.GetFileThumbnail(fileId)
  686. CheckForbiddenStatus(t, resp)
  687. Client.Logout()
  688. _, resp = th.SystemAdminClient.GetFileThumbnail(fileId)
  689. CheckNoError(t, resp)
  690. }
  691. func TestGetFileLink(t *testing.T) {
  692. th := Setup(t).InitBasic()
  693. defer th.TearDown()
  694. Client := th.Client
  695. channel := th.BasicChannel
  696. if *th.App.Config().FileSettings.DriverName == "" {
  697. t.Skip("skipping because no file driver is enabled")
  698. }
  699. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
  700. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) })
  701. data, err := testutils.ReadTestFile("test.png")
  702. require.NoError(t, err)
  703. fileResp, uploadResp := Client.UploadFile(data, channel.Id, "test.png")
  704. CheckNoError(t, uploadResp)
  705. fileId := fileResp.FileInfos[0].Id
  706. _, resp := Client.GetFileLink(fileId)
  707. CheckBadRequestStatus(t, resp)
  708. // Hacky way to assign file to a post (usually would be done by CreatePost call)
  709. err = th.App.Srv().Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id, th.BasicUser.Id)
  710. require.Nil(t, err)
  711. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = false })
  712. _, resp = Client.GetFileLink(fileId)
  713. CheckNotImplementedStatus(t, resp)
  714. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
  715. link, resp := Client.GetFileLink(fileId)
  716. CheckNoError(t, resp)
  717. require.NotEqual(t, "", link, "should've received public link")
  718. _, resp = Client.GetFileLink("junk")
  719. CheckBadRequestStatus(t, resp)
  720. _, resp = Client.GetFileLink(model.NewId())
  721. CheckNotFoundStatus(t, resp)
  722. Client.Logout()
  723. _, resp = Client.GetFileLink(fileId)
  724. CheckUnauthorizedStatus(t, resp)
  725. otherUser := th.CreateUser()
  726. Client.Login(otherUser.Email, otherUser.Password)
  727. _, resp = Client.GetFileLink(fileId)
  728. CheckForbiddenStatus(t, resp)
  729. Client.Logout()
  730. _, resp = th.SystemAdminClient.GetFileLink(fileId)
  731. CheckNoError(t, resp)
  732. fileInfo, err := th.App.Srv().Store.FileInfo().Get(fileId)
  733. require.Nil(t, err)
  734. th.cleanupTestFile(fileInfo)
  735. }
  736. func TestGetFilePreview(t *testing.T) {
  737. th := Setup(t).InitBasic()
  738. defer th.TearDown()
  739. Client := th.Client
  740. channel := th.BasicChannel
  741. if *th.App.Config().FileSettings.DriverName == "" {
  742. t.Skip("skipping because no file driver is enabled")
  743. }
  744. sent, err := testutils.ReadTestFile("test.png")
  745. require.NoError(t, err)
  746. fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
  747. CheckNoError(t, resp)
  748. fileId := fileResp.FileInfos[0].Id
  749. data, resp := Client.GetFilePreview(fileId)
  750. CheckNoError(t, resp)
  751. require.NotEqual(t, 0, len(data), "should not be empty")
  752. _, resp = Client.GetFilePreview("junk")
  753. CheckBadRequestStatus(t, resp)
  754. _, resp = Client.GetFilePreview(model.NewId())
  755. CheckNotFoundStatus(t, resp)
  756. Client.Logout()
  757. _, resp = Client.GetFilePreview(fileId)
  758. CheckUnauthorizedStatus(t, resp)
  759. otherUser := th.CreateUser()
  760. Client.Login(otherUser.Email, otherUser.Password)
  761. _, resp = Client.GetFilePreview(fileId)
  762. CheckForbiddenStatus(t, resp)
  763. Client.Logout()
  764. _, resp = th.SystemAdminClient.GetFilePreview(fileId)
  765. CheckNoError(t, resp)
  766. }
  767. func TestGetFileInfo(t *testing.T) {
  768. th := Setup(t).InitBasic()
  769. defer th.TearDown()
  770. Client := th.Client
  771. user := th.BasicUser
  772. channel := th.BasicChannel
  773. if *th.App.Config().FileSettings.DriverName == "" {
  774. t.Skip("skipping because no file driver is enabled")
  775. }
  776. sent, err := testutils.ReadTestFile("test.png")
  777. require.NoError(t, err)
  778. fileResp, resp := Client.UploadFile(sent, channel.Id, "test.png")
  779. CheckNoError(t, resp)
  780. fileId := fileResp.FileInfos[0].Id
  781. info, resp := Client.GetFileInfo(fileId)
  782. CheckNoError(t, resp)
  783. require.NoError(t, err)
  784. require.Equal(t, fileId, info.Id, "got incorrect file")
  785. require.Equal(t, user.Id, info.CreatorId, "file should be assigned to user")
  786. require.Equal(t, "", info.PostId, "file shouldn't have a post")
  787. require.Equal(t, "", info.Path, "file path shouldn't have been returned to client")
  788. require.Equal(t, "", info.ThumbnailPath, "file thumbnail path shouldn't have been returned to client")
  789. require.Equal(t, "", info.PreviewPath, "file preview path shouldn't have been returned to client")
  790. require.Equal(t, "image/png", info.MimeType, "mime type should've been image/png")
  791. _, resp = Client.GetFileInfo("junk")
  792. CheckBadRequestStatus(t, resp)
  793. _, resp = Client.GetFileInfo(model.NewId())
  794. CheckNotFoundStatus(t, resp)
  795. Client.Logout()
  796. _, resp = Client.GetFileInfo(fileId)
  797. CheckUnauthorizedStatus(t, resp)
  798. otherUser := th.CreateUser()
  799. Client.Login(otherUser.Email, otherUser.Password)
  800. _, resp = Client.GetFileInfo(fileId)
  801. CheckForbiddenStatus(t, resp)
  802. Client.Logout()
  803. _, resp = th.SystemAdminClient.GetFileInfo(fileId)
  804. CheckNoError(t, resp)
  805. }
  806. func TestGetPublicFile(t *testing.T) {
  807. th := Setup(t).InitBasic()
  808. defer th.TearDown()
  809. Client := th.Client
  810. channel := th.BasicChannel
  811. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
  812. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) })
  813. data, err := testutils.ReadTestFile("test.png")
  814. require.NoError(t, err)
  815. fileResp, httpResp := Client.UploadFile(data, channel.Id, "test.png")
  816. CheckNoError(t, httpResp)
  817. fileId := fileResp.FileInfos[0].Id
  818. // Hacky way to assign file to a post (usually would be done by CreatePost call)
  819. err = th.App.Srv().Store.FileInfo().AttachToPost(fileId, th.BasicPost.Id, th.BasicUser.Id)
  820. require.Nil(t, err)
  821. info, err := th.App.Srv().Store.FileInfo().Get(fileId)
  822. require.Nil(t, err)
  823. link := th.App.GeneratePublicLink(Client.Url, info)
  824. resp, err := http.Get(link)
  825. require.NoError(t, err)
  826. require.Equal(t, http.StatusOK, resp.StatusCode, "failed to get image with public link")
  827. resp, err = http.Get(link[:strings.LastIndex(link, "?")])
  828. require.NoError(t, err)
  829. require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should've failed to get image with public link without hash", resp.Status)
  830. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = false })
  831. resp, err = http.Get(link)
  832. require.NoError(t, err)
  833. require.Equal(t, http.StatusNotImplemented, resp.StatusCode, "should've failed to get image with disabled public link")
  834. // test after the salt has changed
  835. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.EnablePublicLink = true })
  836. th.App.UpdateConfig(func(cfg *model.Config) { *cfg.FileSettings.PublicLinkSalt = model.NewRandomString(32) })
  837. resp, err = http.Get(link)
  838. require.NoError(t, err)
  839. require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should've failed to get image with public link after salt changed")
  840. resp, err = http.Get(link)
  841. require.NoError(t, err)
  842. require.Equal(t, http.StatusBadRequest, resp.StatusCode, "should've failed to get image with public link after salt changed")
  843. fileInfo, err := th.App.Srv().Store.FileInfo().Get(fileId)
  844. require.Nil(t, err)
  845. require.Nil(t, th.cleanupTestFile(fileInfo))
  846. th.cleanupTestFile(info)
  847. }