github.com/masterhung0112/hk_server/v5@v5.0.0-20220302090640-ec71aef15e1c/api4/file.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package api4 5 6 import ( 7 "bytes" 8 "crypto/subtle" 9 "io" 10 "mime" 11 "mime/multipart" 12 "net/http" 13 "net/url" 14 "strconv" 15 "strings" 16 "time" 17 18 "github.com/masterhung0112/hk_server/v5/app" 19 "github.com/masterhung0112/hk_server/v5/audit" 20 "github.com/masterhung0112/hk_server/v5/model" 21 "github.com/masterhung0112/hk_server/v5/utils" 22 ) 23 24 const ( 25 FileTeamId = "noteam" 26 27 PreviewImageType = "image/jpeg" 28 ThumbnailImageType = "image/jpeg" 29 ) 30 31 var UnsafeContentTypes = [...]string{ 32 "application/javascript", 33 "application/ecmascript", 34 "text/javascript", 35 "text/ecmascript", 36 "application/x-javascript", 37 "text/html", 38 } 39 40 var MediaContentTypes = [...]string{ 41 "image/jpeg", 42 "image/png", 43 "image/bmp", 44 "image/gif", 45 "image/tiff", 46 "video/avi", 47 "video/mpeg", 48 "video/mp4", 49 "audio/mpeg", 50 "audio/wav", 51 } 52 53 const maxMultipartFormDataBytes = 10 * 1024 // 10Kb 54 55 func (api *API) InitFile() { 56 api.BaseRoutes.Files.Handle("", api.ApiSessionRequired(uploadFileStream)).Methods("POST") 57 api.BaseRoutes.File.Handle("", api.ApiSessionRequiredTrustRequester(getFile)).Methods("GET") 58 api.BaseRoutes.File.Handle("/thumbnail", api.ApiSessionRequiredTrustRequester(getFileThumbnail)).Methods("GET") 59 api.BaseRoutes.File.Handle("/link", api.ApiSessionRequired(getFileLink)).Methods("GET") 60 api.BaseRoutes.File.Handle("/preview", api.ApiSessionRequiredTrustRequester(getFilePreview)).Methods("GET") 61 api.BaseRoutes.File.Handle("/info", api.ApiSessionRequired(getFileInfo)).Methods("GET") 62 63 api.BaseRoutes.Team.Handle("/files/search", api.ApiSessionRequiredDisableWhenBusy(searchFiles)).Methods("POST") 64 65 api.BaseRoutes.PublicFile.Handle("", api.ApiHandler(getPublicFile)).Methods("GET") 66 67 } 68 69 func parseMultipartRequestHeader(req *http.Request) (boundary string, err error) { 70 v := req.Header.Get("Content-Type") 71 if v == "" { 72 return "", http.ErrNotMultipart 73 } 74 d, params, err := mime.ParseMediaType(v) 75 if err != nil || d != "multipart/form-data" { 76 return "", http.ErrNotMultipart 77 } 78 boundary, ok := params["boundary"] 79 if !ok { 80 return "", http.ErrMissingBoundary 81 } 82 83 return boundary, nil 84 } 85 86 func multipartReader(req *http.Request, stream io.Reader) (*multipart.Reader, error) { 87 boundary, err := parseMultipartRequestHeader(req) 88 if err != nil { 89 return nil, err 90 } 91 92 if stream != nil { 93 return multipart.NewReader(stream, boundary), nil 94 } 95 96 return multipart.NewReader(req.Body, boundary), nil 97 } 98 99 func uploadFileStream(c *Context, w http.ResponseWriter, r *http.Request) { 100 if !*c.App.Config().FileSettings.EnableFileAttachments { 101 c.Err = model.NewAppError("uploadFileStream", 102 "api.file.attachments.disabled.app_error", 103 nil, "", http.StatusNotImplemented) 104 return 105 } 106 107 // Parse the post as a regular form (in practice, use the URL values 108 // since we never expect a real application/x-www-form-urlencoded 109 // form). 110 if r.Form == nil { 111 err := r.ParseForm() 112 if err != nil { 113 c.Err = model.NewAppError("uploadFileStream", 114 "api.file.upload_file.read_request.app_error", 115 nil, err.Error(), http.StatusBadRequest) 116 return 117 } 118 } 119 120 if r.ContentLength == 0 { 121 c.Err = model.NewAppError("uploadFileStream", 122 "api.file.upload_file.read_request.app_error", 123 nil, "Content-Length should not be 0", http.StatusBadRequest) 124 return 125 } 126 127 timestamp := time.Now() 128 var fileUploadResponse *model.FileUploadResponse 129 130 _, err := parseMultipartRequestHeader(r) 131 switch err { 132 case nil: 133 fileUploadResponse = uploadFileMultipart(c, r, nil, timestamp) 134 135 case http.ErrNotMultipart: 136 fileUploadResponse = uploadFileSimple(c, r, timestamp) 137 138 default: 139 c.Err = model.NewAppError("uploadFileStream", 140 "api.file.upload_file.read_request.app_error", 141 nil, err.Error(), http.StatusBadRequest) 142 } 143 if c.Err != nil { 144 return 145 } 146 147 // Write the response values to the output upon return 148 w.WriteHeader(http.StatusCreated) 149 w.Write([]byte(fileUploadResponse.ToJson())) 150 } 151 152 // uploadFileSimple uploads a file from a simple POST with the file in the request body 153 func uploadFileSimple(c *Context, r *http.Request, timestamp time.Time) *model.FileUploadResponse { 154 // Simple POST with the file in the body and all metadata in the args. 155 c.RequireChannelId() 156 c.RequireFilename() 157 if c.Err != nil { 158 return nil 159 } 160 161 auditRec := c.MakeAuditRecord("uploadFileSimple", audit.Fail) 162 defer c.LogAuditRec(auditRec) 163 auditRec.AddMeta("channel_id", c.Params.ChannelId) 164 165 if !c.App.SessionHasPermissionToChannel(*c.AppContext.Session(), c.Params.ChannelId, model.PERMISSION_UPLOAD_FILE) { 166 c.SetPermissionError(model.PERMISSION_UPLOAD_FILE) 167 return nil 168 } 169 170 clientId := r.Form.Get("client_id") 171 auditRec.AddMeta("client_id", clientId) 172 173 info, appErr := c.App.UploadFileX(c.AppContext, c.Params.ChannelId, c.Params.Filename, r.Body, 174 app.UploadFileSetTeamId(FileTeamId), 175 app.UploadFileSetUserId(c.AppContext.Session().UserId), 176 app.UploadFileSetTimestamp(timestamp), 177 app.UploadFileSetContentLength(r.ContentLength), 178 app.UploadFileSetClientId(clientId)) 179 if appErr != nil { 180 c.Err = appErr 181 return nil 182 } 183 auditRec.AddMeta("file", info) 184 185 fileUploadResponse := &model.FileUploadResponse{ 186 FileInfos: []*model.FileInfo{info}, 187 } 188 if clientId != "" { 189 fileUploadResponse.ClientIds = []string{clientId} 190 } 191 auditRec.Success() 192 return fileUploadResponse 193 } 194 195 // uploadFileMultipart parses and uploads file(s) from a mime/multipart 196 // request. It pre-buffers up to the first part which is either the (a) 197 // `channel_id` value, or (b) a file. Then in case of (a) it re-processes the 198 // entire message recursively calling itself in stream mode. In case of (b) it 199 // calls to uploadFileMultipartLegacy for legacy support 200 func uploadFileMultipart(c *Context, r *http.Request, asStream io.Reader, timestamp time.Time) *model.FileUploadResponse { 201 202 expectClientIds := true 203 var clientIds []string 204 resp := model.FileUploadResponse{ 205 FileInfos: []*model.FileInfo{}, 206 ClientIds: []string{}, 207 } 208 209 var buf *bytes.Buffer 210 var mr *multipart.Reader 211 var err error 212 if asStream == nil { 213 // We need to buffer until we get the channel_id, or the first file. 214 buf = &bytes.Buffer{} 215 mr, err = multipartReader(r, io.TeeReader(r.Body, buf)) 216 } else { 217 mr, err = multipartReader(r, asStream) 218 } 219 if err != nil { 220 c.Err = model.NewAppError("uploadFileMultipart", 221 "api.file.upload_file.read_request.app_error", 222 nil, err.Error(), http.StatusBadRequest) 223 return nil 224 } 225 226 nFiles := 0 227 NEXT_PART: 228 for { 229 part, err := mr.NextPart() 230 if err == io.EOF { 231 break 232 } 233 if err != nil { 234 c.Err = model.NewAppError("uploadFileMultipart", 235 "api.file.upload_file.read_request.app_error", 236 nil, err.Error(), http.StatusBadRequest) 237 return nil 238 } 239 240 // Parse any form fields in the multipart. 241 formname := part.FormName() 242 if formname == "" { 243 continue 244 } 245 filename := part.FileName() 246 if filename == "" { 247 var b bytes.Buffer 248 _, err = io.CopyN(&b, part, maxMultipartFormDataBytes) 249 if err != nil && err != io.EOF { 250 c.Err = model.NewAppError("uploadFileMultipart", 251 "api.file.upload_file.read_form_value.app_error", 252 map[string]interface{}{"Formname": formname}, 253 err.Error(), http.StatusBadRequest) 254 return nil 255 } 256 v := b.String() 257 258 switch formname { 259 case "channel_id": 260 if c.Params.ChannelId != "" && c.Params.ChannelId != v { 261 c.Err = model.NewAppError("uploadFileMultipart", 262 "api.file.upload_file.multiple_channel_ids.app_error", 263 nil, "", http.StatusBadRequest) 264 return nil 265 } 266 if v != "" { 267 c.Params.ChannelId = v 268 } 269 270 // Got channel_id, re-process the entire post 271 // in the streaming mode. 272 if asStream == nil { 273 return uploadFileMultipart(c, r, io.MultiReader(buf, r.Body), timestamp) 274 } 275 276 case "client_ids": 277 if !expectClientIds { 278 c.SetInvalidParam("client_ids") 279 return nil 280 } 281 clientIds = append(clientIds, v) 282 283 default: 284 c.SetInvalidParam(formname) 285 return nil 286 } 287 288 continue NEXT_PART 289 } 290 291 // A file part. 292 293 if c.Params.ChannelId == "" && asStream == nil { 294 // Got file before channel_id, fall back to legacy buffered mode 295 mr, err = multipartReader(r, io.MultiReader(buf, r.Body)) 296 if err != nil { 297 c.Err = model.NewAppError("uploadFileMultipart", 298 "api.file.upload_file.read_request.app_error", 299 nil, err.Error(), http.StatusBadRequest) 300 return nil 301 } 302 303 return uploadFileMultipartLegacy(c, mr, timestamp) 304 } 305 306 c.RequireChannelId() 307 if c.Err != nil { 308 return nil 309 } 310 if !c.App.SessionHasPermissionToChannel(*c.AppContext.Session(), c.Params.ChannelId, model.PERMISSION_UPLOAD_FILE) { 311 c.SetPermissionError(model.PERMISSION_UPLOAD_FILE) 312 return nil 313 } 314 315 // If there's no clientIds when the first file comes, expect 316 // none later. 317 if nFiles == 0 && len(clientIds) == 0 { 318 expectClientIds = false 319 } 320 321 // Must have a exactly one client ID for each file. 322 clientId := "" 323 if expectClientIds { 324 if nFiles >= len(clientIds) { 325 c.SetInvalidParam("client_ids") 326 return nil 327 } 328 329 clientId = clientIds[nFiles] 330 } 331 332 auditRec := c.MakeAuditRecord("uploadFileMultipart", audit.Fail) 333 auditRec.AddMeta("channel_id", c.Params.ChannelId) 334 auditRec.AddMeta("client_id", clientId) 335 336 info, appErr := c.App.UploadFileX(c.AppContext, c.Params.ChannelId, filename, part, 337 app.UploadFileSetTeamId(FileTeamId), 338 app.UploadFileSetUserId(c.AppContext.Session().UserId), 339 app.UploadFileSetTimestamp(timestamp), 340 app.UploadFileSetContentLength(-1), 341 app.UploadFileSetClientId(clientId)) 342 if appErr != nil { 343 c.Err = appErr 344 c.LogAuditRec(auditRec) 345 return nil 346 } 347 auditRec.AddMeta("file", info) 348 349 auditRec.Success() 350 c.LogAuditRec(auditRec) 351 352 // add to the response 353 resp.FileInfos = append(resp.FileInfos, info) 354 if expectClientIds { 355 resp.ClientIds = append(resp.ClientIds, clientId) 356 } 357 358 nFiles++ 359 } 360 361 // Verify that the number of ClientIds matched the number of files. 362 if expectClientIds && len(clientIds) != nFiles { 363 c.Err = model.NewAppError("uploadFileMultipart", 364 "api.file.upload_file.incorrect_number_of_client_ids.app_error", 365 map[string]interface{}{"NumClientIds": len(clientIds), "NumFiles": nFiles}, 366 "", http.StatusBadRequest) 367 return nil 368 } 369 370 return &resp 371 } 372 373 // uploadFileMultipartLegacy reads, buffers, and then uploads the message, 374 // borrowing from http.ParseMultipartForm. If successful it returns a 375 // *model.FileUploadResponse filled in with the individual model.FileInfo's. 376 func uploadFileMultipartLegacy(c *Context, mr *multipart.Reader, 377 timestamp time.Time) *model.FileUploadResponse { 378 379 // Parse the entire form. 380 form, err := mr.ReadForm(*c.App.Config().FileSettings.MaxFileSize) 381 if err != nil { 382 c.Err = model.NewAppError("uploadFileMultipartLegacy", 383 "api.file.upload_file.read_request.app_error", 384 nil, err.Error(), http.StatusInternalServerError) 385 return nil 386 } 387 388 // get and validate the channel Id, permission to upload there. 389 if len(form.Value["channel_id"]) == 0 { 390 c.SetInvalidParam("channel_id") 391 return nil 392 } 393 channelId := form.Value["channel_id"][0] 394 c.Params.ChannelId = channelId 395 c.RequireChannelId() 396 if c.Err != nil { 397 return nil 398 } 399 if !c.App.SessionHasPermissionToChannel(*c.AppContext.Session(), channelId, model.PERMISSION_UPLOAD_FILE) { 400 c.SetPermissionError(model.PERMISSION_UPLOAD_FILE) 401 return nil 402 } 403 404 // Check that we have either no client IDs, or one per file. 405 clientIds := form.Value["client_ids"] 406 fileHeaders := form.File["files"] 407 if len(clientIds) != 0 && len(clientIds) != len(fileHeaders) { 408 c.Err = model.NewAppError("uploadFilesMultipartBuffered", 409 "api.file.upload_file.incorrect_number_of_client_ids.app_error", 410 map[string]interface{}{"NumClientIds": len(clientIds), "NumFiles": len(fileHeaders)}, 411 "", http.StatusBadRequest) 412 return nil 413 } 414 415 resp := model.FileUploadResponse{ 416 FileInfos: []*model.FileInfo{}, 417 ClientIds: []string{}, 418 } 419 420 for i, fileHeader := range fileHeaders { 421 f, err := fileHeader.Open() 422 if err != nil { 423 c.Err = model.NewAppError("uploadFileMultipartLegacy", 424 "api.file.upload_file.read_request.app_error", 425 nil, err.Error(), http.StatusBadRequest) 426 return nil 427 } 428 429 clientId := "" 430 if len(clientIds) > 0 { 431 clientId = clientIds[i] 432 } 433 434 auditRec := c.MakeAuditRecord("uploadFileMultipartLegacy", audit.Fail) 435 defer c.LogAuditRec(auditRec) 436 auditRec.AddMeta("channel_id", channelId) 437 auditRec.AddMeta("client_id", clientId) 438 439 info, appErr := c.App.UploadFileX(c.AppContext, c.Params.ChannelId, fileHeader.Filename, f, 440 app.UploadFileSetTeamId(FileTeamId), 441 app.UploadFileSetUserId(c.AppContext.Session().UserId), 442 app.UploadFileSetTimestamp(timestamp), 443 app.UploadFileSetContentLength(-1), 444 app.UploadFileSetClientId(clientId)) 445 f.Close() 446 if appErr != nil { 447 c.Err = appErr 448 c.LogAuditRec(auditRec) 449 return nil 450 } 451 auditRec.AddMeta("file", info) 452 453 auditRec.Success() 454 c.LogAuditRec(auditRec) 455 456 resp.FileInfos = append(resp.FileInfos, info) 457 if clientId != "" { 458 resp.ClientIds = append(resp.ClientIds, clientId) 459 } 460 } 461 462 return &resp 463 } 464 465 func getFile(c *Context, w http.ResponseWriter, r *http.Request) { 466 c.RequireFileId() 467 if c.Err != nil { 468 return 469 } 470 471 forceDownload, _ := strconv.ParseBool(r.URL.Query().Get("download")) 472 473 auditRec := c.MakeAuditRecord("getFile", audit.Fail) 474 defer c.LogAuditRec(auditRec) 475 auditRec.AddMeta("force_download", forceDownload) 476 477 info, err := c.App.GetFileInfo(c.Params.FileId) 478 if err != nil { 479 c.Err = err 480 return 481 } 482 auditRec.AddMeta("file", info) 483 484 if info.CreatorId != c.AppContext.Session().UserId && !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), info.PostId, model.PERMISSION_READ_CHANNEL) { 485 c.SetPermissionError(model.PERMISSION_READ_CHANNEL) 486 return 487 } 488 489 fileReader, err := c.App.FileReader(info.Path) 490 if err != nil { 491 c.Err = err 492 c.Err.StatusCode = http.StatusNotFound 493 return 494 } 495 defer fileReader.Close() 496 497 auditRec.Success() 498 499 writeFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) 500 } 501 502 func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) { 503 c.RequireFileId() 504 if c.Err != nil { 505 return 506 } 507 508 forceDownload, _ := strconv.ParseBool(r.URL.Query().Get("download")) 509 info, err := c.App.GetFileInfo(c.Params.FileId) 510 if err != nil { 511 c.Err = err 512 return 513 } 514 515 if info.CreatorId != c.AppContext.Session().UserId && !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), info.PostId, model.PERMISSION_READ_CHANNEL) { 516 c.SetPermissionError(model.PERMISSION_READ_CHANNEL) 517 return 518 } 519 520 if info.ThumbnailPath == "" { 521 c.Err = model.NewAppError("getFileThumbnail", "api.file.get_file_thumbnail.no_thumbnail.app_error", nil, "file_id="+info.Id, http.StatusBadRequest) 522 return 523 } 524 525 fileReader, err := c.App.FileReader(info.ThumbnailPath) 526 if err != nil { 527 c.Err = err 528 c.Err.StatusCode = http.StatusNotFound 529 return 530 } 531 defer fileReader.Close() 532 533 writeFileResponse(info.Name, ThumbnailImageType, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) 534 } 535 536 func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) { 537 c.RequireFileId() 538 if c.Err != nil { 539 return 540 } 541 542 if !*c.App.Config().FileSettings.EnablePublicLink { 543 c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented) 544 return 545 } 546 547 auditRec := c.MakeAuditRecord("getFileLink", audit.Fail) 548 defer c.LogAuditRec(auditRec) 549 550 info, err := c.App.GetFileInfo(c.Params.FileId) 551 if err != nil { 552 c.Err = err 553 return 554 } 555 auditRec.AddMeta("file", info) 556 557 if info.CreatorId != c.AppContext.Session().UserId && !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), info.PostId, model.PERMISSION_READ_CHANNEL) { 558 c.SetPermissionError(model.PERMISSION_READ_CHANNEL) 559 return 560 } 561 562 if info.PostId == "" { 563 c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.no_post.app_error", nil, "file_id="+info.Id, http.StatusBadRequest) 564 return 565 } 566 567 resp := make(map[string]string) 568 link := c.App.GeneratePublicLink(c.GetSiteURLHeader(), info) 569 resp["link"] = link 570 571 auditRec.Success() 572 auditRec.AddMeta("link", link) 573 574 w.Write([]byte(model.MapToJson(resp))) 575 } 576 577 func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) { 578 c.RequireFileId() 579 if c.Err != nil { 580 return 581 } 582 583 forceDownload, _ := strconv.ParseBool(r.URL.Query().Get("download")) 584 info, err := c.App.GetFileInfo(c.Params.FileId) 585 if err != nil { 586 c.Err = err 587 return 588 } 589 590 if info.CreatorId != c.AppContext.Session().UserId && !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), info.PostId, model.PERMISSION_READ_CHANNEL) { 591 c.SetPermissionError(model.PERMISSION_READ_CHANNEL) 592 return 593 } 594 595 if info.PreviewPath == "" { 596 c.Err = model.NewAppError("getFilePreview", "api.file.get_file_preview.no_preview.app_error", nil, "file_id="+info.Id, http.StatusBadRequest) 597 return 598 } 599 600 fileReader, err := c.App.FileReader(info.PreviewPath) 601 if err != nil { 602 c.Err = err 603 c.Err.StatusCode = http.StatusNotFound 604 return 605 } 606 defer fileReader.Close() 607 608 writeFileResponse(info.Name, PreviewImageType, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) 609 } 610 611 func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) { 612 c.RequireFileId() 613 if c.Err != nil { 614 return 615 } 616 617 info, err := c.App.GetFileInfo(c.Params.FileId) 618 if err != nil { 619 c.Err = err 620 return 621 } 622 623 if info.CreatorId != c.AppContext.Session().UserId && !c.App.SessionHasPermissionToChannelByPost(*c.AppContext.Session(), info.PostId, model.PERMISSION_READ_CHANNEL) { 624 c.SetPermissionError(model.PERMISSION_READ_CHANNEL) 625 return 626 } 627 628 w.Header().Set("Cache-Control", "max-age=2592000, private") 629 w.Write([]byte(info.ToJson())) 630 } 631 632 func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) { 633 c.RequireFileId() 634 if c.Err != nil { 635 return 636 } 637 638 if !*c.App.Config().FileSettings.EnablePublicLink { 639 c.Err = model.NewAppError("getPublicFile", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented) 640 return 641 } 642 643 info, err := c.App.GetFileInfo(c.Params.FileId) 644 if err != nil { 645 c.Err = err 646 return 647 } 648 649 hash := r.URL.Query().Get("h") 650 651 if hash == "" { 652 c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) 653 utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey()) 654 return 655 } 656 657 if subtle.ConstantTimeCompare([]byte(hash), []byte(app.GeneratePublicLinkHash(info.Id, *c.App.Config().FileSettings.PublicLinkSalt))) != 1 { 658 c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) 659 utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey()) 660 return 661 } 662 663 fileReader, err := c.App.FileReader(info.Path) 664 if err != nil { 665 c.Err = err 666 c.Err.StatusCode = http.StatusNotFound 667 return 668 } 669 defer fileReader.Close() 670 671 writeFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, false, w, r) 672 } 673 674 func writeFileResponse(filename string, contentType string, contentSize int64, lastModification time.Time, webserverMode string, fileReader io.ReadSeeker, forceDownload bool, w http.ResponseWriter, r *http.Request) { 675 w.Header().Set("Cache-Control", "private, no-cache") 676 w.Header().Set("X-Content-Type-Options", "nosniff") 677 678 if contentSize > 0 { 679 contentSizeStr := strconv.Itoa(int(contentSize)) 680 if webserverMode == "gzip" { 681 w.Header().Set("X-Uncompressed-Content-Length", contentSizeStr) 682 } else { 683 w.Header().Set("Content-Length", contentSizeStr) 684 } 685 } 686 687 if contentType == "" { 688 contentType = "application/octet-stream" 689 } else { 690 for _, unsafeContentType := range UnsafeContentTypes { 691 if strings.HasPrefix(contentType, unsafeContentType) { 692 contentType = "text/plain" 693 break 694 } 695 } 696 } 697 698 w.Header().Set("Content-Type", contentType) 699 700 var toDownload bool 701 if forceDownload { 702 toDownload = true 703 } else { 704 isMediaType := false 705 706 for _, mediaContentType := range MediaContentTypes { 707 if strings.HasPrefix(contentType, mediaContentType) { 708 isMediaType = true 709 break 710 } 711 } 712 713 toDownload = !isMediaType 714 } 715 716 filename = url.PathEscape(filename) 717 718 if toDownload { 719 w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+filename) 720 } else { 721 w.Header().Set("Content-Disposition", "inline;filename=\""+filename+"\"; filename*=UTF-8''"+filename) 722 } 723 724 // prevent file links from being embedded in iframes 725 w.Header().Set("X-Frame-Options", "DENY") 726 w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'") 727 728 http.ServeContent(w, r, filename, lastModification, fileReader) 729 } 730 731 func searchFiles(c *Context, w http.ResponseWriter, r *http.Request) { 732 c.RequireTeamId() 733 if c.Err != nil { 734 return 735 } 736 737 if !c.App.SessionHasPermissionToTeam(*c.AppContext.Session(), c.Params.TeamId, model.PERMISSION_VIEW_TEAM) { 738 c.SetPermissionError(model.PERMISSION_VIEW_TEAM) 739 return 740 } 741 742 params, jsonErr := model.SearchParameterFromJson(r.Body) 743 if jsonErr != nil { 744 c.Err = model.NewAppError("searchFiles", "api.post.search_files.invalid_body.app_error", nil, jsonErr.Error(), http.StatusBadRequest) 745 return 746 } 747 748 if params.Terms == nil || *params.Terms == "" { 749 c.SetInvalidParam("terms") 750 return 751 } 752 terms := *params.Terms 753 754 timeZoneOffset := 0 755 if params.TimeZoneOffset != nil { 756 timeZoneOffset = *params.TimeZoneOffset 757 } 758 759 isOrSearch := false 760 if params.IsOrSearch != nil { 761 isOrSearch = *params.IsOrSearch 762 } 763 764 page := 0 765 if params.Page != nil { 766 page = *params.Page 767 } 768 769 perPage := 60 770 if params.PerPage != nil { 771 perPage = *params.PerPage 772 } 773 774 includeDeletedChannels := false 775 if params.IncludeDeletedChannels != nil { 776 includeDeletedChannels = *params.IncludeDeletedChannels 777 } 778 779 startTime := time.Now() 780 781 results, err := c.App.SearchFilesInTeamForUser(c.AppContext, terms, c.AppContext.Session().UserId, c.Params.TeamId, isOrSearch, includeDeletedChannels, timeZoneOffset, page, perPage) 782 783 elapsedTime := float64(time.Since(startTime)) / float64(time.Second) 784 metrics := c.App.Metrics() 785 if metrics != nil { 786 metrics.IncrementFilesSearchCounter() 787 metrics.ObserveFilesSearchDuration(elapsedTime) 788 } 789 790 if err != nil { 791 c.Err = err 792 return 793 } 794 795 w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") 796 w.Write([]byte(results.ToJson())) 797 }