github.com/vnforks/kid/v5@v5.22.1-0.20200408055009-b89d99c65676/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 "io/ioutil" 11 "mime" 12 "mime/multipart" 13 "net/http" 14 "net/url" 15 "strconv" 16 "strings" 17 "time" 18 19 "github.com/vnforks/kid/v5/app" 20 "github.com/vnforks/kid/v5/audit" 21 "github.com/vnforks/kid/v5/model" 22 "github.com/vnforks/kid/v5/utils" 23 ) 24 25 const ( 26 FILE_BRANCH_ID = "nobranch" 27 28 PREVIEW_IMAGE_TYPE = "image/jpeg" 29 THUMBNAIL_IMAGE_TYPE = "image/jpeg" 30 ) 31 32 var UNSAFE_CONTENT_TYPES = [...]string{ 33 "application/javascript", 34 "application/ecmascript", 35 "text/javascript", 36 "text/ecmascript", 37 "application/x-javascript", 38 "text/html", 39 } 40 41 var MEDIA_CONTENT_TYPES = [...]string{ 42 "image/jpeg", 43 "image/png", 44 "image/bmp", 45 "image/gif", 46 "image/tiff", 47 "video/avi", 48 "video/mpeg", 49 "video/mp4", 50 "audio/mpeg", 51 "audio/wav", 52 } 53 54 const maxUploadDrainBytes = (10 * 1024 * 1024) // 10Mb 55 const maxMultipartFormDataBytes = 10 * 1024 // 10Kb 56 57 func (api *API) InitFile() { 58 api.BaseRoutes.Files.Handle("", api.ApiSessionRequired(uploadFileStream)).Methods("POST") 59 api.BaseRoutes.File.Handle("", api.ApiSessionRequiredTrustRequester(getFile)).Methods("GET") 60 api.BaseRoutes.File.Handle("/thumbnail", api.ApiSessionRequiredTrustRequester(getFileThumbnail)).Methods("GET") 61 api.BaseRoutes.File.Handle("/link", api.ApiSessionRequired(getFileLink)).Methods("GET") 62 api.BaseRoutes.File.Handle("/preview", api.ApiSessionRequiredTrustRequester(getFilePreview)).Methods("GET") 63 api.BaseRoutes.File.Handle("/info", api.ApiSessionRequired(getFileInfo)).Methods("GET") 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 } else { 95 return multipart.NewReader(req.Body, boundary), nil 96 } 97 } 98 99 func uploadFileStream(c *Context, w http.ResponseWriter, r *http.Request) { 100 // Drain any remaining bytes in the request body, up to a limit 101 defer io.CopyN(ioutil.Discard, r.Body, maxUploadDrainBytes) 102 103 if !*c.App.Config().FileSettings.EnableFileAttachments { 104 c.Err = model.NewAppError("uploadFileStream", 105 "api.file.attachments.disabled.app_error", 106 nil, "", http.StatusNotImplemented) 107 return 108 } 109 110 // Parse the post as a regular form (in practice, use the URL values 111 // since we never expect a real application/x-www-form-urlencoded 112 // form). 113 if r.Form == nil { 114 err := r.ParseForm() 115 if err != nil { 116 c.Err = model.NewAppError("uploadFileStream", 117 "api.file.upload_file.read_request.app_error", 118 nil, err.Error(), http.StatusBadRequest) 119 return 120 } 121 } 122 123 timestamp := time.Now() 124 var fileUploadResponse *model.FileUploadResponse 125 126 _, err := parseMultipartRequestHeader(r) 127 switch err { 128 case nil: 129 fileUploadResponse = uploadFileMultipart(c, r, nil, timestamp) 130 131 case http.ErrNotMultipart: 132 fileUploadResponse = uploadFileSimple(c, r, timestamp) 133 134 default: 135 c.Err = model.NewAppError("uploadFileStream", 136 "api.file.upload_file.read_request.app_error", 137 nil, err.Error(), http.StatusBadRequest) 138 } 139 if c.Err != nil { 140 return 141 } 142 143 // Write the response values to the output upon return 144 w.WriteHeader(http.StatusCreated) 145 w.Write([]byte(fileUploadResponse.ToJson())) 146 } 147 148 // uploadFileSimple uploads a file from a simple POST with the file in the request body 149 func uploadFileSimple(c *Context, r *http.Request, timestamp time.Time) *model.FileUploadResponse { 150 // Simple POST with the file in the body and all metadata in the args. 151 c.RequireClassId() 152 c.RequireFilename() 153 if c.Err != nil { 154 return nil 155 } 156 157 auditRec := c.MakeAuditRecord("uploadFileSimple", audit.Fail) 158 defer c.LogAuditRec(auditRec) 159 auditRec.AddMeta("class_id", c.Params.ClassId) 160 auditRec.AddMeta("filename", c.Params.Filename) 161 162 if !c.App.SessionHasPermissionToClass(*c.App.Session(), c.Params.ClassId, model.PERMISSION_UPLOAD_FILE) { 163 c.SetPermissionError(model.PERMISSION_UPLOAD_FILE) 164 return nil 165 } 166 167 clientId := r.Form.Get("client_id") 168 auditRec.AddMeta("client_id", clientId) 169 auditRec.AddMeta("content_length", r.ContentLength) 170 171 info, appErr := c.App.UploadFileX(c.Params.ClassId, c.Params.Filename, r.Body, 172 app.UploadFileSetBranchId(FILE_BRANCH_ID), 173 app.UploadFileSetUserId(c.App.Session().UserId), 174 app.UploadFileSetTimestamp(timestamp), 175 app.UploadFileSetContentLength(r.ContentLength), 176 app.UploadFileSetClientId(clientId)) 177 if appErr != nil { 178 c.Err = appErr 179 return nil 180 } 181 182 fileUploadResponse := &model.FileUploadResponse{ 183 FileInfos: []*model.FileInfo{info}, 184 } 185 if clientId != "" { 186 fileUploadResponse.ClientIds = []string{clientId} 187 } 188 auditRec.Success() 189 return fileUploadResponse 190 } 191 192 // uploadFileMultipart parses and uploads file(s) from a mime/multipart 193 // request. It pre-buffers up to the first part which is either the (a) 194 // `class_id` value, or (b) a file. Then in case of (a) it re-processes the 195 // entire message recursively calling itself in stream mode. In case of (b) it 196 // calls to uploadFileMultipartLegacy for legacy support 197 func uploadFileMultipart(c *Context, r *http.Request, asStream io.Reader, timestamp time.Time) *model.FileUploadResponse { 198 199 expectClientIds := true 200 var clientIds []string 201 resp := model.FileUploadResponse{ 202 FileInfos: []*model.FileInfo{}, 203 ClientIds: []string{}, 204 } 205 206 var buf *bytes.Buffer 207 var mr *multipart.Reader 208 var err error 209 if asStream == nil { 210 // We need to buffer until we get the class_id, or the first file. 211 buf = &bytes.Buffer{} 212 mr, err = multipartReader(r, io.TeeReader(r.Body, buf)) 213 } else { 214 mr, err = multipartReader(r, asStream) 215 } 216 if err != nil { 217 c.Err = model.NewAppError("uploadFileMultipart", 218 "api.file.upload_file.read_request.app_error", 219 nil, err.Error(), http.StatusBadRequest) 220 return nil 221 } 222 223 nFiles := 0 224 NEXT_PART: 225 for { 226 part, err := mr.NextPart() 227 if err == io.EOF { 228 break 229 } 230 if err != nil { 231 c.Err = model.NewAppError("uploadFileMultipart", 232 "api.file.upload_file.read_request.app_error", 233 nil, err.Error(), http.StatusBadRequest) 234 return nil 235 } 236 237 // Parse any form fields in the multipart. 238 formname := part.FormName() 239 if formname == "" { 240 continue 241 } 242 filename := part.FileName() 243 if filename == "" { 244 var b bytes.Buffer 245 _, err = io.CopyN(&b, part, maxMultipartFormDataBytes) 246 if err != nil && err != io.EOF { 247 c.Err = model.NewAppError("uploadFileMultipart", 248 "api.file.upload_file.read_form_value.app_error", 249 map[string]interface{}{"Formname": formname}, 250 err.Error(), http.StatusBadRequest) 251 return nil 252 } 253 v := b.String() 254 255 switch formname { 256 case "class_id": 257 if c.Params.ClassId != "" && c.Params.ClassId != v { 258 c.Err = model.NewAppError("uploadFileMultipart", 259 "api.file.upload_file.multiple_class_ids.app_error", 260 nil, "", http.StatusBadRequest) 261 return nil 262 } 263 if v != "" { 264 c.Params.ClassId = v 265 } 266 267 // Got class_id, re-process the entire post 268 // in the streaming mode. 269 if asStream == nil { 270 return uploadFileMultipart(c, r, io.MultiReader(buf, r.Body), timestamp) 271 } 272 273 case "client_ids": 274 if !expectClientIds { 275 c.SetInvalidParam("client_ids") 276 return nil 277 } 278 clientIds = append(clientIds, v) 279 280 default: 281 c.SetInvalidParam(formname) 282 return nil 283 } 284 285 continue NEXT_PART 286 } 287 288 // A file part. 289 290 if c.Params.ClassId == "" && asStream == nil { 291 // Got file before class_id, fall back to legacy buffered mode 292 mr, err = multipartReader(r, io.MultiReader(buf, r.Body)) 293 if err != nil { 294 c.Err = model.NewAppError("uploadFileMultipart", 295 "api.file.upload_file.read_request.app_error", 296 nil, err.Error(), http.StatusBadRequest) 297 return nil 298 } 299 300 return uploadFileMultipartLegacy(c, mr, timestamp) 301 } 302 303 c.RequireClassId() 304 if c.Err != nil { 305 return nil 306 } 307 if !c.App.SessionHasPermissionToClass(*c.App.Session(), c.Params.ClassId, model.PERMISSION_UPLOAD_FILE) { 308 c.SetPermissionError(model.PERMISSION_UPLOAD_FILE) 309 return nil 310 } 311 312 // If there's no clientIds when the first file comes, expect 313 // none later. 314 if nFiles == 0 && len(clientIds) == 0 { 315 expectClientIds = false 316 } 317 318 // Must have a exactly one client ID for each file. 319 clientId := "" 320 if expectClientIds { 321 if nFiles >= len(clientIds) { 322 c.SetInvalidParam("client_ids") 323 return nil 324 } 325 326 clientId = clientIds[nFiles] 327 } 328 329 auditRec := c.MakeAuditRecord("uploadFileMultipart", audit.Fail) 330 auditRec.AddMeta("class_id", c.Params.ClassId) 331 auditRec.AddMeta("filename", filename) 332 auditRec.AddMeta("client_id", clientId) 333 334 info, appErr := c.App.UploadFileX(c.Params.ClassId, filename, part, 335 app.UploadFileSetBranchId(FILE_BRANCH_ID), 336 app.UploadFileSetUserId(c.App.Session().UserId), 337 app.UploadFileSetTimestamp(timestamp), 338 app.UploadFileSetContentLength(-1), 339 app.UploadFileSetClientId(clientId)) 340 if appErr != nil { 341 c.Err = appErr 342 c.LogAuditRec(auditRec) 343 return nil 344 } 345 auditRec.Success() 346 c.LogAuditRec(auditRec) 347 348 // add to the response 349 resp.FileInfos = append(resp.FileInfos, info) 350 if expectClientIds { 351 resp.ClientIds = append(resp.ClientIds, clientId) 352 } 353 354 nFiles++ 355 } 356 357 // Verify that the number of ClientIds matched the number of files. 358 if expectClientIds && len(clientIds) != nFiles { 359 c.Err = model.NewAppError("uploadFileMultipart", 360 "api.file.upload_file.incorrect_number_of_client_ids.app_error", 361 map[string]interface{}{"NumClientIds": len(clientIds), "NumFiles": nFiles}, 362 "", http.StatusBadRequest) 363 return nil 364 } 365 366 return &resp 367 } 368 369 // uploadFileMultipartLegacy reads, buffers, and then uploads the message, 370 // borrowing from http.ParseMultipartForm. If successful it returns a 371 // *model.FileUploadResponse filled in with the individual model.FileInfo's. 372 func uploadFileMultipartLegacy(c *Context, mr *multipart.Reader, 373 timestamp time.Time) *model.FileUploadResponse { 374 375 // Parse the entire form. 376 form, err := mr.ReadForm(*c.App.Config().FileSettings.MaxFileSize) 377 if err != nil { 378 c.Err = model.NewAppError("uploadFileMultipartLegacy", 379 "api.file.upload_file.read_request.app_error", 380 nil, err.Error(), http.StatusInternalServerError) 381 return nil 382 } 383 384 // get and validate the class Id, permission to upload there. 385 if len(form.Value["class_id"]) == 0 { 386 c.SetInvalidParam("class_id") 387 return nil 388 } 389 classId := form.Value["class_id"][0] 390 c.Params.ClassId = classId 391 c.RequireClassId() 392 if c.Err != nil { 393 return nil 394 } 395 if !c.App.SessionHasPermissionToClass(*c.App.Session(), classId, model.PERMISSION_UPLOAD_FILE) { 396 c.SetPermissionError(model.PERMISSION_UPLOAD_FILE) 397 return nil 398 } 399 400 // Check that we have either no client IDs, or one per file. 401 clientIds := form.Value["client_ids"] 402 fileHeaders := form.File["files"] 403 if len(clientIds) != 0 && len(clientIds) != len(fileHeaders) { 404 c.Err = model.NewAppError("uploadFilesMultipartBuffered", 405 "api.file.upload_file.incorrect_number_of_client_ids.app_error", 406 map[string]interface{}{"NumClientIds": len(clientIds), "NumFiles": len(fileHeaders)}, 407 "", http.StatusBadRequest) 408 return nil 409 } 410 411 resp := model.FileUploadResponse{ 412 FileInfos: []*model.FileInfo{}, 413 ClientIds: []string{}, 414 } 415 416 for i, fileHeader := range fileHeaders { 417 f, err := fileHeader.Open() 418 if err != nil { 419 c.Err = model.NewAppError("uploadFileMultipartLegacy", 420 "api.file.upload_file.read_request.app_error", 421 nil, err.Error(), http.StatusBadRequest) 422 return nil 423 } 424 425 clientId := "" 426 if len(clientIds) > 0 { 427 clientId = clientIds[i] 428 } 429 430 auditRec := c.MakeAuditRecord("uploadFileMultipartLegacy", audit.Fail) 431 defer c.LogAuditRec(auditRec) 432 auditRec.AddMeta("class_id", classId) 433 auditRec.AddMeta("filename", fileHeader.Filename) 434 auditRec.AddMeta("client_id", clientId) 435 436 info, appErr := c.App.UploadFileX(c.Params.ClassId, fileHeader.Filename, f, 437 app.UploadFileSetBranchId(FILE_BRANCH_ID), 438 app.UploadFileSetUserId(c.App.Session().UserId), 439 app.UploadFileSetTimestamp(timestamp), 440 app.UploadFileSetContentLength(-1), 441 app.UploadFileSetClientId(clientId)) 442 f.Close() 443 if appErr != nil { 444 c.Err = appErr 445 c.LogAuditRec(auditRec) 446 return nil 447 } 448 449 auditRec.Success() 450 c.LogAuditRec(auditRec) 451 452 resp.FileInfos = append(resp.FileInfos, info) 453 if clientId != "" { 454 resp.ClientIds = append(resp.ClientIds, clientId) 455 } 456 } 457 458 return &resp 459 } 460 461 func getFile(c *Context, w http.ResponseWriter, r *http.Request) { 462 c.RequireFileId() 463 if c.Err != nil { 464 return 465 } 466 467 forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download")) 468 if convErr != nil { 469 forceDownload = false 470 } 471 472 info, err := c.App.GetFileInfo(c.Params.FileId) 473 if err != nil { 474 c.Err = err 475 return 476 } 477 478 if info.CreatorId != c.App.Session().UserId && !c.App.SessionHasPermissionToClassByPost(*c.App.Session(), info.PostId, model.PERMISSION_READ_CLASS) { 479 c.SetPermissionError(model.PERMISSION_READ_CLASS) 480 return 481 } 482 483 fileReader, err := c.App.FileReader(info.Path) 484 if err != nil { 485 c.Err = err 486 c.Err.StatusCode = http.StatusNotFound 487 return 488 } 489 defer fileReader.Close() 490 491 err = writeFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) 492 if err != nil { 493 c.Err = err 494 return 495 } 496 } 497 498 func getFileThumbnail(c *Context, w http.ResponseWriter, r *http.Request) { 499 c.RequireFileId() 500 if c.Err != nil { 501 return 502 } 503 504 forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download")) 505 if convErr != nil { 506 forceDownload = false 507 } 508 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.App.Session().UserId && !c.App.SessionHasPermissionToClassByPost(*c.App.Session(), info.PostId, model.PERMISSION_READ_CLASS) { 516 c.SetPermissionError(model.PERMISSION_READ_CLASS) 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 err = writeFileResponse(info.Name, THUMBNAIL_IMAGE_TYPE, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) 534 if err != nil { 535 c.Err = err 536 return 537 } 538 } 539 540 func getFileLink(c *Context, w http.ResponseWriter, r *http.Request) { 541 c.RequireFileId() 542 if c.Err != nil { 543 return 544 } 545 546 if !*c.App.Config().FileSettings.EnablePublicLink { 547 c.Err = model.NewAppError("getPublicLink", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented) 548 return 549 } 550 551 info, err := c.App.GetFileInfo(c.Params.FileId) 552 if err != nil { 553 c.Err = err 554 return 555 } 556 557 if info.CreatorId != c.App.Session().UserId && !c.App.SessionHasPermissionToClassByPost(*c.App.Session(), info.PostId, model.PERMISSION_READ_CLASS) { 558 c.SetPermissionError(model.PERMISSION_READ_CLASS) 559 return 560 } 561 562 if len(info.PostId) == 0 { 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 resp["link"] = c.App.GeneratePublicLink(c.GetSiteURLHeader(), info) 569 570 w.Write([]byte(model.MapToJson(resp))) 571 } 572 573 func getFilePreview(c *Context, w http.ResponseWriter, r *http.Request) { 574 c.RequireFileId() 575 if c.Err != nil { 576 return 577 } 578 579 forceDownload, convErr := strconv.ParseBool(r.URL.Query().Get("download")) 580 if convErr != nil { 581 forceDownload = false 582 } 583 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.App.Session().UserId && !c.App.SessionHasPermissionToClassByPost(*c.App.Session(), info.PostId, model.PERMISSION_READ_CLASS) { 591 c.SetPermissionError(model.PERMISSION_READ_CLASS) 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 err = writeFileResponse(info.Name, PREVIEW_IMAGE_TYPE, 0, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, forceDownload, w, r) 609 if err != nil { 610 c.Err = err 611 return 612 } 613 } 614 615 func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) { 616 c.RequireFileId() 617 if c.Err != nil { 618 return 619 } 620 621 info, err := c.App.GetFileInfo(c.Params.FileId) 622 if err != nil { 623 c.Err = err 624 return 625 } 626 627 if info.CreatorId != c.App.Session().UserId && !c.App.SessionHasPermissionToClassByPost(*c.App.Session(), info.PostId, model.PERMISSION_READ_CLASS) { 628 c.SetPermissionError(model.PERMISSION_READ_CLASS) 629 return 630 } 631 632 w.Header().Set("Cache-Control", "max-age=2592000, public") 633 w.Write([]byte(info.ToJson())) 634 } 635 636 func getPublicFile(c *Context, w http.ResponseWriter, r *http.Request) { 637 c.RequireFileId() 638 if c.Err != nil { 639 return 640 } 641 642 if !*c.App.Config().FileSettings.EnablePublicLink { 643 c.Err = model.NewAppError("getPublicFile", "api.file.get_public_link.disabled.app_error", nil, "", http.StatusNotImplemented) 644 return 645 } 646 647 info, err := c.App.GetFileInfo(c.Params.FileId) 648 if err != nil { 649 c.Err = err 650 return 651 } 652 653 hash := r.URL.Query().Get("h") 654 655 if len(hash) == 0 { 656 c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) 657 utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey()) 658 return 659 } 660 661 if subtle.ConstantTimeCompare([]byte(hash), []byte(app.GeneratePublicLinkHash(info.Id, *c.App.Config().FileSettings.PublicLinkSalt))) != 1 { 662 c.Err = model.NewAppError("getPublicFile", "api.file.get_file.public_invalid.app_error", nil, "", http.StatusBadRequest) 663 utils.RenderWebAppError(c.App.Config(), w, r, c.Err, c.App.AsymmetricSigningKey()) 664 return 665 } 666 667 fileReader, err := c.App.FileReader(info.Path) 668 if err != nil { 669 c.Err = err 670 c.Err.StatusCode = http.StatusNotFound 671 } 672 defer fileReader.Close() 673 674 err = writeFileResponse(info.Name, info.MimeType, info.Size, time.Unix(0, info.UpdateAt*int64(1000*1000)), *c.App.Config().ServiceSettings.WebserverMode, fileReader, false, w, r) 675 if err != nil { 676 c.Err = err 677 return 678 } 679 } 680 681 func writeFileResponse(filename string, contentType string, contentSize int64, lastModification time.Time, webserverMode string, fileReader io.ReadSeeker, forceDownload bool, w http.ResponseWriter, r *http.Request) *model.AppError { 682 w.Header().Set("Cache-Control", "private, no-cache") 683 w.Header().Set("X-Content-Type-Options", "nosniff") 684 685 if contentSize > 0 { 686 contentSizeStr := strconv.Itoa(int(contentSize)) 687 if webserverMode == "gzip" { 688 w.Header().Set("X-Uncompressed-Content-Length", contentSizeStr) 689 } else { 690 w.Header().Set("Content-Length", contentSizeStr) 691 } 692 } 693 694 if contentType == "" { 695 contentType = "application/octet-stream" 696 } else { 697 for _, unsafeContentType := range UNSAFE_CONTENT_TYPES { 698 if strings.HasPrefix(contentType, unsafeContentType) { 699 contentType = "text/plain" 700 break 701 } 702 } 703 } 704 705 w.Header().Set("Content-Type", contentType) 706 707 var toDownload bool 708 if forceDownload { 709 toDownload = true 710 } else { 711 isMediaType := false 712 713 for _, mediaContentType := range MEDIA_CONTENT_TYPES { 714 if strings.HasPrefix(contentType, mediaContentType) { 715 isMediaType = true 716 break 717 } 718 } 719 720 toDownload = !isMediaType 721 } 722 723 filename = url.PathEscape(filename) 724 725 if toDownload { 726 w.Header().Set("Content-Disposition", "attachment;filename=\""+filename+"\"; filename*=UTF-8''"+filename) 727 } else { 728 w.Header().Set("Content-Disposition", "inline;filename=\""+filename+"\"; filename*=UTF-8''"+filename) 729 } 730 731 // prevent file links from being embedded in iframes 732 w.Header().Set("X-Frame-Options", "DENY") 733 w.Header().Set("Content-Security-Policy", "Frame-ancestors 'none'") 734 735 http.ServeContent(w, r, filename, lastModification, fileReader) 736 737 return nil 738 }