2015-10-08 12:27:09 -04:00
// Copyright (c) 2015 Mattermost, Inc. All Rights Reserved.
2015-06-14 23:53:32 -08:00
// See License.txt for license information.
package api
import (
"bytes"
l4g "code.google.com/p/log4go"
"fmt"
2015-09-23 15:57:49 -07:00
"github.com/disintegration/imaging"
2015-06-14 23:53:32 -08:00
"github.com/goamz/goamz/aws"
"github.com/goamz/goamz/s3"
"github.com/gorilla/mux"
"github.com/mattermost/platform/model"
"github.com/mattermost/platform/utils"
2015-09-25 11:46:43 -04:00
"github.com/mssola/user_agent"
2015-09-17 11:09:13 -04:00
"github.com/rwcarlsen/goexif/exif"
2015-07-08 11:50:10 -04:00
_ "golang.org/x/image/bmp"
2015-06-14 23:53:32 -08:00
"image"
2015-09-24 08:33:15 -07:00
"image/color"
"image/draw"
2015-06-14 23:53:32 -08:00
_ "image/gif"
"image/jpeg"
"io"
2015-07-16 08:54:09 -04:00
"io/ioutil"
2015-10-17 01:58:34 +02:00
"mime"
2015-06-14 23:53:32 -08:00
"net/http"
"net/url"
2015-07-16 08:54:09 -04:00
"os"
2015-06-14 23:53:32 -08:00
"path/filepath"
"strconv"
"strings"
"time"
)
2015-09-18 18:00:09 -04:00
const (
/ *
EXIF Image Orientations
1 2 3 4 5 6 7 8
888888 888888 88 88 8888888888 88 88 8888888888
88 88 88 88 88 88 88 88 88 88 88 88
8888 8888 8888 8888 88 8888888888 8888888888 88
88 88 88 88
88 88 888888 888888
* /
Upright = 1
UprightMirrored = 2
UpsideDown = 3
UpsideDownMirrored = 4
RotatedCWMirrored = 5
RotatedCCW = 6
RotatedCCWMirrored = 7
RotatedCW = 8
)
2015-08-24 15:03:52 -07:00
var fileInfoCache * utils . Cache = utils . NewLru ( 1000 )
2015-06-14 23:53:32 -08:00
func InitFile ( r * mux . Router ) {
2015-07-16 08:54:09 -04:00
l4g . Debug ( "Initializing file api routes" )
2015-06-14 23:53:32 -08:00
sr := r . PathPrefix ( "/files" ) . Subrouter ( )
sr . Handle ( "/upload" , ApiUserRequired ( uploadFile ) ) . Methods ( "POST" )
2015-08-24 15:03:52 -07:00
sr . Handle ( "/get/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}" , ApiAppHandler ( getFile ) ) . Methods ( "GET" )
sr . Handle ( "/get_info/{channel_id:[A-Za-z0-9]+}/{user_id:[A-Za-z0-9]+}/{filename:([A-Za-z0-9]+/)?.+(\\.[A-Za-z0-9]{3,})?}" , ApiAppHandler ( getFileInfo ) ) . Methods ( "GET" )
2015-06-14 23:53:32 -08:00
sr . Handle ( "/get_public_link" , ApiUserRequired ( getPublicLink ) ) . Methods ( "POST" )
2015-08-26 12:49:07 -04:00
sr . Handle ( "/get_export" , ApiUserRequired ( getExport ) ) . Methods ( "GET" )
2015-06-14 23:53:32 -08:00
}
func uploadFile ( c * Context , w http . ResponseWriter , r * http . Request ) {
2015-09-23 13:47:10 -07:00
if len ( utils . Cfg . FileSettings . DriverName ) == 0 {
2015-09-21 17:34:13 -07:00
c . Err = model . NewAppError ( "uploadFile" , "Unable to upload file. Image storage is not configured." , "" )
2015-06-14 23:53:32 -08:00
c . Err . StatusCode = http . StatusNotImplemented
return
}
err := r . ParseMultipartForm ( model . MAX_FILE_SIZE )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
m := r . MultipartForm
props := m . Value
if len ( props [ "channel_id" ] ) == 0 {
c . SetInvalidParam ( "uploadFile" , "channel_id" )
return
}
channelId := props [ "channel_id" ] [ 0 ]
if len ( channelId ) == 0 {
c . SetInvalidParam ( "uploadFile" , "channel_id" )
return
}
cchan := Srv . Store . Channel ( ) . CheckPermissionsTo ( c . Session . TeamId , channelId , c . Session . UserId )
files := m . File [ "files" ]
resStruct := & model . FileUploadResponse {
2015-08-10 12:05:45 -04:00
Filenames : [ ] string { } ,
ClientIds : [ ] string { } ,
}
2015-06-14 23:53:32 -08:00
imageNameList := [ ] string { }
imageDataList := [ ] [ ] byte { }
if ! c . HasPermissionsToChannel ( cchan , "uploadFile" ) {
return
}
2015-09-17 09:01:33 -04:00
for i := range files {
2015-06-14 23:53:32 -08:00
file , err := files [ i ] . Open ( )
defer file . Close ( )
if err != nil {
http . Error ( w , err . Error ( ) , http . StatusInternalServerError )
return
}
buf := bytes . NewBuffer ( nil )
io . Copy ( buf , file )
2015-07-21 15:18:17 -04:00
filename := filepath . Base ( files [ i ] . Filename )
2015-06-14 23:53:32 -08:00
uid := model . NewId ( )
2015-07-21 15:18:17 -04:00
path := "teams/" + c . Session . TeamId + "/channels/" + channelId + "/users/" + c . Session . UserId + "/" + uid + "/" + filename
2015-06-14 23:53:32 -08:00
2015-07-16 08:54:09 -04:00
if err := writeFile ( buf . Bytes ( ) , path ) ; err != nil {
c . Err = err
return
2015-06-14 23:53:32 -08:00
}
2015-07-16 08:54:09 -04:00
if model . IsFileExtImage ( filepath . Ext ( files [ i ] . Filename ) ) {
2015-07-21 15:18:17 -04:00
imageNameList = append ( imageNameList , uid + "/" + filename )
2015-07-16 08:54:09 -04:00
imageDataList = append ( imageDataList , buf . Bytes ( ) )
2015-06-14 23:53:32 -08:00
}
2015-07-21 15:18:17 -04:00
encName := utils . UrlEncode ( filename )
2015-07-17 15:55:06 -04:00
fileUrl := "/" + channelId + "/" + c . Session . UserId + "/" + uid + "/" + encName
2015-06-14 23:53:32 -08:00
resStruct . Filenames = append ( resStruct . Filenames , fileUrl )
}
2015-08-10 12:05:45 -04:00
for _ , clientId := range props [ "client_ids" ] {
resStruct . ClientIds = append ( resStruct . ClientIds , clientId )
}
2015-10-14 16:56:13 -07:00
handleImagesAndForget ( imageNameList , imageDataList , c . Session . TeamId , channelId , c . Session . UserId )
2015-06-14 23:53:32 -08:00
w . Write ( [ ] byte ( resStruct . ToJson ( ) ) )
}
2015-10-14 16:56:13 -07:00
func handleImagesAndForget ( filenames [ ] string , fileData [ ] [ ] byte , teamId , channelId , userId string ) {
2015-06-14 23:53:32 -08:00
go func ( ) {
dest := "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/"
for i , filename := range filenames {
name := filename [ : strings . LastIndex ( filename , "." ) ]
go func ( ) {
// Decode image bytes into Image object
2015-09-24 08:33:15 -07:00
img , imgType , err := image . Decode ( bytes . NewReader ( fileData [ i ] ) )
2015-06-14 23:53:32 -08:00
if err != nil {
l4g . Error ( "Unable to decode image channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
return
}
2015-09-17 11:09:13 -04:00
width := img . Bounds ( ) . Dx ( )
height := img . Bounds ( ) . Dy ( )
// Get the image's orientation and ignore any errors since not all images will have orientation data
orientation , _ := getImageOrientation ( fileData [ i ] )
2015-09-24 08:33:15 -07:00
if imgType == "png" {
dst := image . NewRGBA ( img . Bounds ( ) )
draw . Draw ( dst , dst . Bounds ( ) , image . NewUniform ( color . White ) , image . Point { } , draw . Src )
draw . Draw ( dst , dst . Bounds ( ) , img , img . Bounds ( ) . Min , draw . Over )
img = dst
}
2015-09-18 18:00:09 -04:00
switch orientation {
2015-09-23 15:57:49 -07:00
case UprightMirrored :
img = imaging . FlipH ( img )
case UpsideDown :
img = imaging . Rotate180 ( img )
case UpsideDownMirrored :
img = imaging . FlipV ( img )
case RotatedCWMirrored :
img = imaging . Transpose ( img )
case RotatedCCW :
img = imaging . Rotate270 ( img )
case RotatedCCWMirrored :
img = imaging . Transverse ( img )
case RotatedCW :
img = imaging . Rotate90 ( img )
2015-09-17 11:09:13 -04:00
}
2015-06-14 23:53:32 -08:00
// Create thumbnail
go func ( ) {
2015-09-23 13:47:10 -07:00
thumbWidth := float64 ( utils . Cfg . FileSettings . ThumbnailWidth )
thumbHeight := float64 ( utils . Cfg . FileSettings . ThumbnailHeight )
2015-09-17 11:09:13 -04:00
imgWidth := float64 ( width )
imgHeight := float64 ( height )
2015-07-29 10:09:11 -04:00
2015-06-14 23:53:32 -08:00
var thumbnail image . Image
2015-07-29 10:09:11 -04:00
if imgHeight < thumbHeight && imgWidth < thumbWidth {
2015-06-14 23:53:32 -08:00
thumbnail = img
2015-07-29 10:09:11 -04:00
} else if imgHeight / imgWidth < thumbHeight / thumbWidth {
2015-09-23 15:57:49 -07:00
thumbnail = imaging . Resize ( img , 0 , utils . Cfg . FileSettings . ThumbnailHeight , imaging . Lanczos )
2015-07-29 10:09:11 -04:00
} else {
2015-09-23 15:57:49 -07:00
thumbnail = imaging . Resize ( img , utils . Cfg . FileSettings . ThumbnailWidth , 0 , imaging . Lanczos )
2015-06-14 23:53:32 -08:00
}
buf := new ( bytes . Buffer )
err = jpeg . Encode ( buf , thumbnail , & jpeg . Options { Quality : 90 } )
if err != nil {
l4g . Error ( "Unable to encode image as jpeg channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
return
}
2015-07-16 12:50:38 -04:00
if err := writeFile ( buf . Bytes ( ) , dest + name + "_thumb.jpg" ) ; err != nil {
l4g . Error ( "Unable to upload thumbnail channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
2015-06-14 23:53:32 -08:00
return
}
} ( )
// Create preview
go func ( ) {
var preview image . Image
2015-09-23 13:47:10 -07:00
if width > int ( utils . Cfg . FileSettings . PreviewWidth ) {
2015-09-23 15:57:49 -07:00
preview = imaging . Resize ( img , utils . Cfg . FileSettings . PreviewWidth , utils . Cfg . FileSettings . PreviewHeight , imaging . Lanczos )
2015-06-14 23:53:32 -08:00
} else {
preview = img
}
buf := new ( bytes . Buffer )
2015-07-16 08:54:09 -04:00
err = jpeg . Encode ( buf , preview , & jpeg . Options { Quality : 90 } )
2015-06-14 23:53:32 -08:00
if err != nil {
l4g . Error ( "Unable to encode image as preview jpg channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
return
}
2015-07-16 12:50:38 -04:00
if err := writeFile ( buf . Bytes ( ) , dest + name + "_preview.jpg" ) ; err != nil {
l4g . Error ( "Unable to upload preview channelId=%v userId=%v filename=%v err=%v" , channelId , userId , filename , err )
2015-06-14 23:53:32 -08:00
return
}
} ( )
} ( )
}
} ( )
}
2015-09-17 11:09:13 -04:00
func getImageOrientation ( imageData [ ] byte ) ( int , error ) {
if exifData , err := exif . Decode ( bytes . NewReader ( imageData ) ) ; err != nil {
2015-09-18 18:00:09 -04:00
return Upright , err
2015-09-17 11:09:13 -04:00
} else {
if tag , err := exifData . Get ( "Orientation" ) ; err != nil {
2015-09-18 18:00:09 -04:00
return Upright , err
2015-09-17 11:09:13 -04:00
} else {
orientation , err := tag . Int ( 0 )
if err != nil {
2015-09-18 18:00:09 -04:00
return Upright , err
2015-09-17 11:09:13 -04:00
} else {
return orientation , nil
}
}
}
}
2015-06-14 23:53:32 -08:00
type ImageGetResult struct {
Error error
ImageData [ ] byte
}
2015-08-24 15:03:52 -07:00
func getFileInfo ( c * Context , w http . ResponseWriter , r * http . Request ) {
2015-09-23 13:47:10 -07:00
if len ( utils . Cfg . FileSettings . DriverName ) == 0 {
2015-09-21 17:34:13 -07:00
c . Err = model . NewAppError ( "uploadFile" , "Unable to get file info. Image storage is not configured." , "" )
2015-08-24 15:03:52 -07:00
c . Err . StatusCode = http . StatusNotImplemented
return
}
params := mux . Vars ( r )
channelId := params [ "channel_id" ]
if len ( channelId ) != 26 {
c . SetInvalidParam ( "getFileInfo" , "channel_id" )
return
}
userId := params [ "user_id" ]
if len ( userId ) != 26 {
c . SetInvalidParam ( "getFileInfo" , "user_id" )
return
}
filename := params [ "filename" ]
if len ( filename ) == 0 {
c . SetInvalidParam ( "getFileInfo" , "filename" )
return
}
cchan := Srv . Store . Channel ( ) . CheckPermissionsTo ( c . Session . TeamId , channelId , c . Session . UserId )
path := "teams/" + c . Session . TeamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
size := ""
if s , ok := fileInfoCache . Get ( path ) ; ok {
size = s . ( string )
} else {
fileData := make ( chan [ ] byte )
2015-10-14 16:56:13 -07:00
getFileAndForget ( path , fileData )
2015-08-24 15:03:52 -07:00
f := <- fileData
if f == nil {
c . Err = model . NewAppError ( "getFileInfo" , "Could not find file." , "path=" + path )
c . Err . StatusCode = http . StatusNotFound
return
}
size = strconv . Itoa ( len ( f ) )
fileInfoCache . Add ( path , size )
}
if ! c . HasPermissionsToChannel ( cchan , "getFileInfo" ) {
return
}
w . Header ( ) . Set ( "Cache-Control" , "max-age=2592000, public" )
2015-10-17 01:58:34 +02:00
var mimeType string
ext := filepath . Ext ( filename )
if model . IsFileExtImage ( ext ) {
mimeType = model . GetImageMimeType ( ext )
} else {
mimeType = mime . TypeByExtension ( ext )
}
2015-08-24 15:03:52 -07:00
result := make ( map [ string ] string )
result [ "filename" ] = filename
result [ "size" ] = size
2015-10-17 01:58:34 +02:00
result [ "mime" ] = mimeType
2015-08-24 15:03:52 -07:00
w . Write ( [ ] byte ( model . MapToJson ( result ) ) )
}
2015-06-14 23:53:32 -08:00
func getFile ( c * Context , w http . ResponseWriter , r * http . Request ) {
2015-09-23 13:47:10 -07:00
if len ( utils . Cfg . FileSettings . DriverName ) == 0 {
2015-09-21 17:34:13 -07:00
c . Err = model . NewAppError ( "uploadFile" , "Unable to get file. Image storage is not configured." , "" )
2015-06-14 23:53:32 -08:00
c . Err . StatusCode = http . StatusNotImplemented
return
}
params := mux . Vars ( r )
channelId := params [ "channel_id" ]
if len ( channelId ) != 26 {
c . SetInvalidParam ( "getFile" , "channel_id" )
return
}
userId := params [ "user_id" ]
if len ( userId ) != 26 {
c . SetInvalidParam ( "getFile" , "user_id" )
return
}
filename := params [ "filename" ]
if len ( filename ) == 0 {
c . SetInvalidParam ( "getFile" , "filename" )
return
}
hash := r . URL . Query ( ) . Get ( "h" )
data := r . URL . Query ( ) . Get ( "d" )
teamId := r . URL . Query ( ) . Get ( "t" )
cchan := Srv . Store . Channel ( ) . CheckPermissionsTo ( c . Session . TeamId , channelId , c . Session . UserId )
path := ""
if len ( teamId ) == 26 {
path = "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
} else {
path = "teams/" + c . Session . TeamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
}
fileData := make ( chan [ ] byte )
2015-10-14 16:56:13 -07:00
getFileAndForget ( path , fileData )
2015-06-14 23:53:32 -08:00
if len ( hash ) > 0 && len ( data ) > 0 && len ( teamId ) == 26 {
2015-09-23 13:47:10 -07:00
if ! model . ComparePassword ( hash , fmt . Sprintf ( "%v:%v" , data , utils . Cfg . FileSettings . PublicLinkSalt ) ) {
2015-06-14 23:53:32 -08:00
c . Err = model . NewAppError ( "getFile" , "The public link does not appear to be valid" , "" )
return
}
props := model . MapFromJson ( strings . NewReader ( data ) )
t , err := strconv . ParseInt ( props [ "time" ] , 10 , 64 )
if err != nil || model . GetMillis ( ) - t > 1000 * 60 * 60 * 24 * 7 { // one week
c . Err = model . NewAppError ( "getFile" , "The public link has expired" , "" )
return
}
} else if ! c . HasPermissionsToChannel ( cchan , "getFile" ) {
return
}
f := <- fileData
if f == nil {
2015-07-16 08:54:09 -04:00
c . Err = model . NewAppError ( "getFile" , "Could not find file." , "path=" + path )
2015-06-14 23:53:32 -08:00
c . Err . StatusCode = http . StatusNotFound
return
}
w . Header ( ) . Set ( "Cache-Control" , "max-age=2592000, public" )
2015-08-27 11:05:18 -07:00
w . Header ( ) . Set ( "Content-Length" , strconv . Itoa ( len ( f ) ) )
2015-10-06 09:09:52 -04:00
w . Header ( ) . Del ( "Content-Type" ) // Content-Type will be set automatically by the http writer
2015-09-25 11:46:43 -04:00
2015-10-09 16:45:40 -04:00
// attach extra headers to trigger a download on IE, Edge, and Safari
2015-09-25 11:46:43 -04:00
ua := user_agent . New ( r . UserAgent ( ) )
bname , _ := ua . Browser ( )
2015-10-09 16:45:40 -04:00
if bname == "Edge" || bname == "Internet Explorer" || bname == "Safari" {
2015-09-25 11:46:43 -04:00
// trim off anything before the final / so we just get the file's name
parts := strings . Split ( filename , "/" )
w . Header ( ) . Set ( "Content-Type" , "application/octet-stream" )
w . Header ( ) . Set ( "Content-Disposition" , "attachment;filename=\"" + parts [ len ( parts ) - 1 ] + "\"" )
}
2015-08-24 15:03:52 -07:00
w . Write ( f )
2015-06-14 23:53:32 -08:00
}
2015-10-14 16:56:13 -07:00
func getFileAndForget ( path string , fileData chan [ ] byte ) {
2015-06-14 23:53:32 -08:00
go func ( ) {
2015-07-16 08:54:09 -04:00
data , getErr := readFile ( path )
2015-06-14 23:53:32 -08:00
if getErr != nil {
2015-07-17 15:55:06 -04:00
l4g . Error ( getErr )
2015-06-14 23:53:32 -08:00
fileData <- nil
} else {
fileData <- data
}
} ( )
}
func getPublicLink ( c * Context , w http . ResponseWriter , r * http . Request ) {
2015-09-23 13:47:10 -07:00
if len ( utils . Cfg . FileSettings . DriverName ) == 0 {
2015-09-21 17:34:13 -07:00
c . Err = model . NewAppError ( "uploadFile" , "Unable to get link. Image storage is not configured." , "" )
c . Err . StatusCode = http . StatusNotImplemented
return
}
2015-09-23 13:47:10 -07:00
if ! utils . Cfg . FileSettings . EnablePublicLink {
2015-06-14 23:53:32 -08:00
c . Err = model . NewAppError ( "getPublicLink" , "Public links have been disabled" , "" )
c . Err . StatusCode = http . StatusForbidden
}
props := model . MapFromJson ( r . Body )
filename := props [ "filename" ]
if len ( filename ) == 0 {
c . SetInvalidParam ( "getPublicLink" , "filename" )
return
}
matches := model . PartialUrlRegex . FindAllStringSubmatch ( filename , - 1 )
2015-07-21 19:21:05 -04:00
if len ( matches ) == 0 || len ( matches [ 0 ] ) < 4 {
2015-06-14 23:53:32 -08:00
c . SetInvalidParam ( "getPublicLink" , "filename" )
return
}
2015-07-21 19:21:05 -04:00
channelId := matches [ 0 ] [ 1 ]
userId := matches [ 0 ] [ 2 ]
filename = matches [ 0 ] [ 3 ]
2015-06-14 23:53:32 -08:00
cchan := Srv . Store . Channel ( ) . CheckPermissionsTo ( c . Session . TeamId , channelId , c . Session . UserId )
newProps := make ( map [ string ] string )
newProps [ "filename" ] = filename
newProps [ "time" ] = fmt . Sprintf ( "%v" , model . GetMillis ( ) )
data := model . MapToJson ( newProps )
2015-09-23 13:47:10 -07:00
hash := model . HashPassword ( fmt . Sprintf ( "%v:%v" , data , utils . Cfg . FileSettings . PublicLinkSalt ) )
2015-06-14 23:53:32 -08:00
2015-07-21 19:21:05 -04:00
url := fmt . Sprintf ( "%s/api/v1/files/get/%s/%s/%s?d=%s&h=%s&t=%s" , c . GetSiteURL ( ) , channelId , userId , filename , url . QueryEscape ( data ) , url . QueryEscape ( hash ) , c . Session . TeamId )
2015-06-14 23:53:32 -08:00
if ! c . HasPermissionsToChannel ( cchan , "getPublicLink" ) {
return
}
rData := make ( map [ string ] string )
rData [ "public_link" ] = url
w . Write ( [ ] byte ( model . MapToJson ( rData ) ) )
}
2015-07-16 08:54:09 -04:00
2015-08-26 12:49:07 -04:00
func getExport ( c * Context , w http . ResponseWriter , r * http . Request ) {
2015-09-30 11:30:11 -04:00
if ! c . HasPermissionsToTeam ( c . Session . TeamId , "export" ) || ! c . IsTeamAdmin ( ) {
2015-08-26 12:49:07 -04:00
c . Err = model . NewAppError ( "getExport" , "Only a team admin can retrieve exported data." , "userId=" + c . Session . UserId )
c . Err . StatusCode = http . StatusForbidden
return
}
data , err := readFile ( EXPORT_PATH + EXPORT_FILENAME )
if err != nil {
c . Err = model . NewAppError ( "getExport" , "Unable to retrieve exported file. Please re-export" , err . Error ( ) )
return
}
w . Header ( ) . Set ( "Content-Disposition" , "attachment; filename=" + EXPORT_FILENAME )
w . Header ( ) . Set ( "Content-Type" , "application/octet-stream" )
w . Write ( data )
}
2015-07-16 08:54:09 -04:00
func writeFile ( f [ ] byte , path string ) * model . AppError {
2015-09-23 13:47:10 -07:00
if utils . Cfg . FileSettings . DriverName == model . IMAGE_DRIVER_S3 {
2015-07-16 08:54:09 -04:00
var auth aws . Auth
2015-09-23 13:47:10 -07:00
auth . AccessKey = utils . Cfg . FileSettings . AmazonS3AccessKeyId
auth . SecretKey = utils . Cfg . FileSettings . AmazonS3SecretAccessKey
2015-07-16 08:54:09 -04:00
2015-09-23 13:47:10 -07:00
s := s3 . New ( auth , aws . Regions [ utils . Cfg . FileSettings . AmazonS3Region ] )
bucket := s . Bucket ( utils . Cfg . FileSettings . AmazonS3Bucket )
2015-07-16 08:54:09 -04:00
ext := filepath . Ext ( path )
var err error
if model . IsFileExtImage ( ext ) {
options := s3 . Options { }
err = bucket . Put ( path , f , model . GetImageMimeType ( ext ) , s3 . Private , options )
} else {
options := s3 . Options { }
err = bucket . Put ( path , f , "binary/octet-stream" , s3 . Private , options )
}
if err != nil {
return model . NewAppError ( "writeFile" , "Encountered an error writing to S3" , err . Error ( ) )
}
2015-09-23 13:47:10 -07:00
} else if utils . Cfg . FileSettings . DriverName == model . IMAGE_DRIVER_LOCAL {
if err := os . MkdirAll ( filepath . Dir ( utils . Cfg . FileSettings . Directory + path ) , 0774 ) ; err != nil {
2015-07-16 08:54:09 -04:00
return model . NewAppError ( "writeFile" , "Encountered an error creating the directory for the new file" , err . Error ( ) )
}
2015-09-23 13:47:10 -07:00
if err := ioutil . WriteFile ( utils . Cfg . FileSettings . Directory + path , f , 0644 ) ; err != nil {
2015-07-16 08:54:09 -04:00
return model . NewAppError ( "writeFile" , "Encountered an error writing to local server storage" , err . Error ( ) )
}
} else {
return model . NewAppError ( "writeFile" , "File storage not configured properly. Please configure for either S3 or local server file storage." , "" )
}
return nil
}
func readFile ( path string ) ( [ ] byte , * model . AppError ) {
2015-09-23 13:47:10 -07:00
if utils . Cfg . FileSettings . DriverName == model . IMAGE_DRIVER_S3 {
2015-07-16 08:54:09 -04:00
var auth aws . Auth
2015-09-23 13:47:10 -07:00
auth . AccessKey = utils . Cfg . FileSettings . AmazonS3AccessKeyId
auth . SecretKey = utils . Cfg . FileSettings . AmazonS3SecretAccessKey
2015-07-16 08:54:09 -04:00
2015-09-23 13:47:10 -07:00
s := s3 . New ( auth , aws . Regions [ utils . Cfg . FileSettings . AmazonS3Region ] )
bucket := s . Bucket ( utils . Cfg . FileSettings . AmazonS3Bucket )
2015-07-16 08:54:09 -04:00
// try to get the file from S3 with some basic retry logic
tries := 0
for {
tries ++
f , err := bucket . Get ( path )
if f != nil {
return f , nil
} else if tries >= 3 {
return nil , model . NewAppError ( "readFile" , "Unable to get file from S3" , "path=" + path + ", err=" + err . Error ( ) )
}
time . Sleep ( 3000 * time . Millisecond )
}
2015-09-23 13:47:10 -07:00
} else if utils . Cfg . FileSettings . DriverName == model . IMAGE_DRIVER_LOCAL {
if f , err := ioutil . ReadFile ( utils . Cfg . FileSettings . Directory + path ) ; err != nil {
2015-07-16 08:54:09 -04:00
return nil , model . NewAppError ( "readFile" , "Encountered an error reading from local server storage" , err . Error ( ) )
} else {
return f , nil
}
} else {
return nil , model . NewAppError ( "readFile" , "File storage not configured properly. Please configure for either S3 or local server file storage." , "" )
}
}
2015-08-26 12:49:07 -04:00
func openFileWriteStream ( path string ) ( io . Writer , * model . AppError ) {
2015-09-23 13:47:10 -07:00
if utils . Cfg . FileSettings . DriverName == model . IMAGE_DRIVER_S3 {
2015-08-26 12:49:07 -04:00
return nil , model . NewAppError ( "openFileWriteStream" , "S3 is not supported." , "" )
2015-09-23 13:47:10 -07:00
} else if utils . Cfg . FileSettings . DriverName == model . IMAGE_DRIVER_LOCAL {
if err := os . MkdirAll ( filepath . Dir ( utils . Cfg . FileSettings . Directory + path ) , 0774 ) ; err != nil {
2015-08-26 12:49:07 -04:00
return nil , model . NewAppError ( "openFileWriteStream" , "Encountered an error creating the directory for the new file" , err . Error ( ) )
}
2015-09-23 13:47:10 -07:00
if fileHandle , err := os . Create ( utils . Cfg . FileSettings . Directory + path ) ; err != nil {
2015-08-26 12:49:07 -04:00
return nil , model . NewAppError ( "openFileWriteStream" , "Encountered an error writing to local server storage" , err . Error ( ) )
} else {
fileHandle . Chmod ( 0644 )
return fileHandle , nil
}
}
return nil , model . NewAppError ( "openFileWriteStream" , "File storage not configured properly. Please configure for either S3 or local server file storage." , "" )
}
func closeFileWriteStream ( file io . Writer ) {
file . ( * os . File ) . Close ( )
}