func readFile(path string) ([]byte, *model.AppError) { if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) // 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) } } else if utils.Cfg.ServiceSettings.UseLocalStorage && len(utils.Cfg.ServiceSettings.StorageDirectory) > 0 { if f, err := ioutil.ReadFile(utils.Cfg.ServiceSettings.StorageDirectory + path); err != nil { 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.", "") } }
func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["id"] if result := <-Srv.Store.User().Get(id); result.Err != nil { c.Err = result.Err return } else { var img []byte var err *model.AppError if !utils.IsS3Configured() { img, err = createProfileImage(result.Data.(*model.User).Username, id) if err != nil { c.Err = err return } } else { var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) path := "teams/" + c.Session.TeamId + "/users/" + id + "/profile.png" if data, getErr := bucket.Get(path); getErr != nil { img, err = createProfileImage(result.Data.(*model.User).Username, id) if err != nil { c.Err = err return } options := s3.Options{} if err := bucket.Put(path, img, "image", s3.Private, options); err != nil { c.Err = model.NewAppError("getImage", "Couldn't upload default profile image", err.Error()) return } } else { img = data } } if c.Session.UserId == id { w.Header().Set("Cache-Control", "max-age=300, public") // 5 mins } else { w.Header().Set("Cache-Control", "max-age=86400, public") // 24 hrs } w.Write(img) } }
func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) { if !utils.Cfg.TeamSettings.AllowPublicLink { c.Err = model.NewAppError("getPublicLink", "Public links have been disabled", "") c.Err.StatusCode = http.StatusForbidden } if !utils.IsS3Configured() { c.Err = model.NewAppError("getPublicLink", "Unable to get link. Amazon S3 not configured. ", "") c.Err.StatusCode = http.StatusNotImplemented return } props := model.MapFromJson(r.Body) filename := props["filename"] if len(filename) == 0 { c.SetInvalidParam("getPublicLink", "filename") return } matches := model.PartialUrlRegex.FindAllStringSubmatch(filename, -1) if len(matches) == 0 || len(matches[0]) < 5 { c.SetInvalidParam("getPublicLink", "filename") return } getType := matches[0][1] channelId := matches[0][2] userId := matches[0][3] filename = matches[0][4] 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) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) url := fmt.Sprintf("%s/api/v1/files/%s/%s/%s/%s?d=%s&h=%s&t=%s", c.TeamUrl, getType, channelId, userId, filename, url.QueryEscape(data), url.QueryEscape(hash), c.Session.TeamId) if !c.HasPermissionsToChannel(cchan, "getPublicLink") { return } rData := make(map[string]string) rData["public_link"] = url w.Write([]byte(model.MapToJson(rData))) }
func TestUserCreateImage(t *testing.T) { Setup() b, err := createProfileImage("Corey Hulen", "eo1zkdr96pdj98pjmq8zy35wba") if err != nil { t.Fatal(err) } rdr := bytes.NewReader(b) img, _, err2 := image.Decode(rdr) if err2 != nil { t.Fatal(err) } colorful := color.RGBA{116, 49, 196, 255} if img.At(1, 1) != colorful { t.Fatal("Failed to create correct color") } team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "*****@*****.**", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "*****@*****.**", Nickname: "Corey Hulen", Password: "******"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) Client.LoginByEmail(team.Name, user.Email, "pwd") Client.DoGet("/users/"+user.Id+"/image", "", "") if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) if err := bucket.Del("teams/" + user.TeamId + "/users/" + user.Id + "/profile.png"); err != nil { t.Fatal(err) } } else { path := utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + user.TeamId + "/users/" + user.Id + "/profile.png" if err := os.Remove(path); err != nil { t.Fatal("Couldn't remove file at " + path) } } }
func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { params := mux.Vars(r) id := params["id"] if result := <-Srv.Store.User().Get(id); result.Err != nil { c.Err = result.Err return } else { var img []byte if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { var err *model.AppError if img, err = createProfileImage(result.Data.(*model.User).Username, id); err != nil { c.Err = err return } } else { path := "teams/" + c.Session.TeamId + "/users/" + id + "/profile.png" if data, err := readFile(path); err != nil { if img, err = createProfileImage(result.Data.(*model.User).Username, id); err != nil { c.Err = err return } if err := writeFile(img, path); err != nil { c.Err = err return } } else { img = data } } if c.Session.UserId == id { w.Header().Set("Cache-Control", "max-age=300, public") // 5 mins } else { w.Header().Set("Cache-Control", "max-age=86400, public") // 24 hrs } w.Write(img) } }
func openFileWriteStream(path string) (io.Writer, *model.AppError) { if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { return nil, model.NewAppError("openFileWriteStream", "S3 is not supported.", "") } else if utils.Cfg.ServiceSettings.UseLocalStorage && len(utils.Cfg.ServiceSettings.StorageDirectory) > 0 { if err := os.MkdirAll(filepath.Dir(utils.Cfg.ServiceSettings.StorageDirectory+path), 0774); err != nil { return nil, model.NewAppError("openFileWriteStream", "Encountered an error creating the directory for the new file", err.Error()) } if fileHandle, err := os.Create(utils.Cfg.ServiceSettings.StorageDirectory + path); err != nil { 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 writeFile(f []byte, path string) *model.AppError { if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) 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()) } } else if utils.Cfg.ServiceSettings.UseLocalStorage && len(utils.Cfg.ServiceSettings.StorageDirectory) > 0 { if err := os.MkdirAll(filepath.Dir(utils.Cfg.ServiceSettings.StorageDirectory+path), 0774); err != nil { return model.NewAppError("writeFile", "Encountered an error creating the directory for the new file", err.Error()) } if err := ioutil.WriteFile(utils.Cfg.ServiceSettings.StorageDirectory+path, f, 0644); err != nil { 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 uploadFile(c *Context, w http.ResponseWriter, r *http.Request) { if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { c.Err = model.NewAppError("uploadFile", "Unable to upload file. Amazon S3 not configured and local server storage turned off. ", "") 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{ Filenames: []string{}, ClientIds: []string{}, } imageNameList := []string{} imageDataList := [][]byte{} if !c.HasPermissionsToChannel(cchan, "uploadFile") { return } for i, _ := range files { 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) filename := filepath.Base(files[i].Filename) uid := model.NewId() path := "teams/" + c.Session.TeamId + "/channels/" + channelId + "/users/" + c.Session.UserId + "/" + uid + "/" + filename if err := writeFile(buf.Bytes(), path); err != nil { c.Err = err return } if model.IsFileExtImage(filepath.Ext(files[i].Filename)) { imageNameList = append(imageNameList, uid+"/"+filename) imageDataList = append(imageDataList, buf.Bytes()) } encName := utils.UrlEncode(filename) fileUrl := "/" + channelId + "/" + c.Session.UserId + "/" + uid + "/" + encName resStruct.Filenames = append(resStruct.Filenames, fileUrl) } for _, clientId := range props["client_ids"] { resStruct.ClientIds = append(resStruct.ClientIds, clientId) } fireAndForgetHandleImages(imageNameList, imageDataList, c.Session.TeamId, channelId, c.Session.UserId) w.Write([]byte(resStruct.ToJson())) }
func getFile(c *Context, w http.ResponseWriter, r *http.Request) { if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { c.Err = model.NewAppError("getFile", "Unable to get file. Amazon S3 not configured and local server storage turned off. ", "") 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) asyncGetFile(path, fileData) if len(hash) > 0 && len(data) > 0 && len(teamId) == 26 { if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) { 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 { c.Err = model.NewAppError("getFile", "Could not find file.", "path="+path) c.Err.StatusCode = http.StatusNotFound return } w.Header().Set("Cache-Control", "max-age=2592000, public") w.Header().Set("Content-Length", strconv.Itoa(len(f))) w.Header().Set("Content-Type", "") // need to provide proper Content-Type in the future w.Write(f) }
func getFileInfo(c *Context, w http.ResponseWriter, r *http.Request) { if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { c.Err = model.NewAppError("getFileInfo", "Unable to get file info. Amazon S3 not configured and local server storage turned off. ", "") 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) asyncGetFile(path, fileData) 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") result := make(map[string]string) result["filename"] = filename result["size"] = size w.Write([]byte(model.MapToJson(result))) }
func TestGetPublicLink(t *testing.T) { Setup() team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "*****@*****.**", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "*****@*****.**", FullName: "Corey Hulen", Password: "******"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) Srv.Store.User().VerifyEmail(user1.Id) user2 := &model.User{TeamId: team.Id, Email: model.NewId() + "*****@*****.**", FullName: "Corey Hulen", Password: "******"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) Srv.Store.User().VerifyEmail(user2.Id) Client.LoginByEmail(team.Domain, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) if utils.IsS3Configured() { body := &bytes.Buffer{} writer := multipart.NewWriter(body) part, err := writer.CreateFormFile("files", "test.png") if err != nil { t.Fatal(err) } path := utils.FindDir("web/static/images") file, err := os.Open(path + "/test.png") if err != nil { t.Fatal(err) } defer file.Close() _, err = io.Copy(part, file) if err != nil { t.Fatal(err) } field, err := writer.CreateFormField("channel_id") if err != nil { t.Fatal(err) } _, err = field.Write([]byte(channel1.Id)) if err != nil { t.Fatal(err) } err = writer.Close() if err != nil { t.Fatal(err) } resp, upErr := Client.UploadFile("/files/upload", body.Bytes(), writer.FormDataContentType()) if upErr != nil { t.Fatal(upErr) } filenames := resp.Data.(*model.FileUploadResponse).Filenames post1 := &model.Post{ChannelId: channel1.Id, Message: "a" + model.NewId() + "a", Filenames: filenames} rpost1, postErr := Client.CreatePost(post1) if postErr != nil { t.Fatal(postErr) } if rpost1.Data.(*model.Post).Filenames[0] != filenames[0] { t.Fatal("filenames don't match") } // wait a bit for files to ready time.Sleep(5 * time.Second) data := make(map[string]string) data["filename"] = filenames[0] if _, err := Client.GetPublicLink(data); err != nil { t.Fatal(err) } data["filename"] = "junk" if _, err := Client.GetPublicLink(data); err == nil { t.Fatal("Should have errored - bad file path") } Client.LoginByEmail(team.Domain, user2.Email, "pwd") data["filename"] = filenames[0] if _, err := Client.GetPublicLink(data); err == nil { t.Fatal("should have errored, user not member of channel") } // perform clean-up on s3 var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) fileId := strings.Split(filenames[0], ".")[0] if err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + rpost1.Data.(*model.Post).UserId + "/" + filenames[0]); err != nil { t.Fatal(err) } if err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + rpost1.Data.(*model.Post).UserId + "/" + fileId + "_thumb.jpg"); err != nil { t.Fatal(err) } if err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + rpost1.Data.(*model.Post).UserId + "/" + fileId + "_preview.png"); err != nil { t.Fatal(err) } } else { data := make(map[string]string) if _, err := Client.GetPublicLink(data); err.StatusCode != http.StatusNotImplemented { t.Fatal("Status code should have been 501 - Not Implemented") } } }
func TestUploadFile(t *testing.T) { Setup() team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "*****@*****.**", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "*****@*****.**", FullName: "Corey Hulen", Password: "******"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) Srv.Store.User().VerifyEmail(user1.Id) Client.LoginByEmail(team.Domain, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) body := &bytes.Buffer{} writer := multipart.NewWriter(body) part, err := writer.CreateFormFile("files", "test.png") if err != nil { t.Fatal(err) } path := utils.FindDir("web/static/images") file, err := os.Open(path + "/test.png") defer file.Close() _, err = io.Copy(part, file) if err != nil { t.Fatal(err) } field, err := writer.CreateFormField("channel_id") if err != nil { t.Fatal(err) } _, err = field.Write([]byte(channel1.Id)) if err != nil { t.Fatal(err) } err = writer.Close() if err != nil { t.Fatal(err) } resp, appErr := Client.UploadFile("/files/upload", body.Bytes(), writer.FormDataContentType()) if utils.IsS3Configured() { if appErr != nil { t.Fatal(appErr) } filenames := resp.Data.(*model.FileUploadResponse).Filenames var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) fileId := strings.Split(filenames[0], ".")[0] // wait a bit for files to ready time.Sleep(5 * time.Second) err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filenames[0]) if err != nil { t.Fatal(err) } err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg") if err != nil { t.Fatal(err) } err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.png") if err != nil { t.Fatal(err) } } else { if appErr == nil { t.Fatal("S3 not configured, should have failed") } } }
func TestUserUploadProfileImage(t *testing.T) { Setup() team := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "*****@*****.**", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) user := &model.User{TeamId: team.Id, Email: strings.ToLower(model.NewId()) + "*****@*****.**", Nickname: "Corey Hulen", Password: "******"} user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) store.Must(Srv.Store.User().VerifyEmail(user.Id)) if utils.IsS3Configured() || utils.Cfg.ServiceSettings.UseLocalStorage { body := &bytes.Buffer{} writer := multipart.NewWriter(body) if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr == nil { t.Fatal("Should have errored") } Client.LoginByEmail(team.Name, user.Email, "pwd") if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr == nil { t.Fatal("Should have errored") } part, err := writer.CreateFormFile("blargh", "test.png") if err != nil { t.Fatal(err) } path := utils.FindDir("web/static/images") file, err := os.Open(path + "/test.png") if err != nil { t.Fatal(err) } defer file.Close() _, err = io.Copy(part, file) if err != nil { t.Fatal(err) } if err := writer.Close(); err != nil { t.Fatal(err) } if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr == nil { t.Fatal("Should have errored") } file2, err := os.Open(path + "/test.png") if err != nil { t.Fatal(err) } defer file2.Close() body = &bytes.Buffer{} writer = multipart.NewWriter(body) part, err = writer.CreateFormFile("image", "test.png") if err != nil { t.Fatal(err) } if _, err := io.Copy(part, file2); err != nil { t.Fatal(err) } if err := writer.Close(); err != nil { t.Fatal(err) } if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr != nil { t.Fatal(upErr) } Client.DoGet("/users/"+user.Id+"/image", "", "") if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) if err := bucket.Del("teams/" + user.TeamId + "/users/" + user.Id + "/profile.png"); err != nil { t.Fatal(err) } } else { path := utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + user.TeamId + "/users/" + user.Id + "/profile.png" if err := os.Remove(path); err != nil { t.Fatal("Couldn't remove file at " + path) } } } else { body := &bytes.Buffer{} writer := multipart.NewWriter(body) if _, upErr := Client.UploadFile("/users/newimage", body.Bytes(), writer.FormDataContentType()); upErr.StatusCode != http.StatusNotImplemented { t.Fatal("Should have failed with 501 - Not Implemented") } } }
func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage { c.Err = model.NewAppError("uploadProfileImage", "Unable to upload file. Amazon S3 not configured and local server storage turned off. ", "") c.Err.StatusCode = http.StatusNotImplemented return } if err := r.ParseMultipartForm(10000000); err != nil { c.Err = model.NewAppError("uploadProfileImage", "Could not parse multipart form", "") return } m := r.MultipartForm imageArray, ok := m.File["image"] if !ok { c.Err = model.NewAppError("uploadProfileImage", "No file under 'image' in request", "") c.Err.StatusCode = http.StatusBadRequest return } if len(imageArray) <= 0 { c.Err = model.NewAppError("uploadProfileImage", "Empty array under 'image' in request", "") c.Err.StatusCode = http.StatusBadRequest return } imageData := imageArray[0] file, err := imageData.Open() defer file.Close() if err != nil { c.Err = model.NewAppError("uploadProfileImage", "Could not open image file", err.Error()) return } // Decode image into Image object img, _, err := image.Decode(file) if err != nil { c.Err = model.NewAppError("uploadProfileImage", "Could not decode profile image", err.Error()) return } // Scale profile image img = resize.Resize(utils.Cfg.ImageSettings.ProfileWidth, utils.Cfg.ImageSettings.ProfileHeight, img, resize.Lanczos3) buf := new(bytes.Buffer) err = png.Encode(buf, img) if err != nil { c.Err = model.NewAppError("uploadProfileImage", "Could not encode profile image", err.Error()) return } path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png" if err := writeFile(buf.Bytes(), path); err != nil { c.Err = model.NewAppError("uploadProfileImage", "Couldn't upload profile image", "") return } Srv.Store.User().UpdateLastPictureUpdate(c.Session.UserId) c.LogAudit("") }
func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { if !utils.IsS3Configured() { c.Err = model.NewAppError("uploadProfileImage", "Unable to upload image. Amazon S3 not configured. ", "") c.Err.StatusCode = http.StatusNotImplemented return } if err := r.ParseMultipartForm(10000000); err != nil { c.Err = model.NewAppError("uploadProfileImage", "Could not parse multipart form", "") return } var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) m := r.MultipartForm imageArray, ok := m.File["image"] if !ok { c.Err = model.NewAppError("uploadProfileImage", "No file under 'image' in request", "") c.Err.StatusCode = http.StatusBadRequest return } if len(imageArray) <= 0 { c.Err = model.NewAppError("uploadProfileImage", "Empty array under 'image' in request", "") c.Err.StatusCode = http.StatusBadRequest return } imageData := imageArray[0] file, err := imageData.Open() defer file.Close() if err != nil { c.Err = model.NewAppError("uploadProfileImage", "Could not open image file", err.Error()) return } // Decode image into Image object img, _, err := image.Decode(file) if err != nil { c.Err = model.NewAppError("uploadProfileImage", "Could not decode profile image", err.Error()) return } // Scale profile image img = resize.Resize(utils.Cfg.ImageSettings.ProfileWidth, utils.Cfg.ImageSettings.ProfileHeight, img, resize.Lanczos3) buf := new(bytes.Buffer) err = png.Encode(buf, img) if err != nil { c.Err = model.NewAppError("uploadProfileImage", "Could not encode profile image", err.Error()) return } path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png" options := s3.Options{} if err := bucket.Put(path, buf.Bytes(), "image", s3.Private, options); err != nil { c.Err = model.NewAppError("uploadProfileImage", "Couldn't upload profile image", "") return } Srv.Store.User().UpdateUpdateAt(c.Session.UserId) c.LogAudit("") }
func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) { if !utils.IsS3Configured() { c.Err = model.NewAppError("uploadFile", "Unable to upload file. Amazon S3 not configured. ", "") c.Err.StatusCode = http.StatusNotImplemented return } err := r.ParseMultipartForm(model.MAX_FILE_SIZE) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) 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{ Filenames: []string{}} imageNameList := []string{} imageDataList := [][]byte{} if !c.HasPermissionsToChannel(cchan, "uploadFile") { return } for i, _ := range files { 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) ext := filepath.Ext(files[i].Filename) uid := model.NewId() path := "teams/" + c.Session.TeamId + "/channels/" + channelId + "/users/" + c.Session.UserId + "/" + uid + "/" + files[i].Filename if model.IsFileExtImage(ext) { options := s3.Options{} err = bucket.Put(path, buf.Bytes(), model.GetImageMimeType(ext), s3.Private, options) imageNameList = append(imageNameList, uid+"/"+files[i].Filename) imageDataList = append(imageDataList, buf.Bytes()) } else { options := s3.Options{} err = bucket.Put(path, buf.Bytes(), "binary/octet-stream", s3.Private, options) } if err != nil { c.Err = model.NewAppError("uploadFile", "Unable to upload file. ", err.Error()) return } fileUrl := c.TeamUrl + "/api/v1/files/get/" + channelId + "/" + c.Session.UserId + "/" + uid + "/" + url.QueryEscape(files[i].Filename) resStruct.Filenames = append(resStruct.Filenames, fileUrl) } fireAndForgetHandleImages(imageNameList, imageDataList, c.Session.TeamId, channelId, c.Session.UserId) w.Write([]byte(resStruct.ToJson())) }
func TestGetFile(t *testing.T) { Setup() team := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "*****@*****.**", Type: model.TEAM_OPEN} team = Client.Must(Client.CreateTeam(team)).Data.(*model.Team) user1 := &model.User{TeamId: team.Id, Email: model.NewId() + "*****@*****.**", FullName: "Corey Hulen", Password: "******"} user1 = Client.Must(Client.CreateUser(user1, "")).Data.(*model.User) Srv.Store.User().VerifyEmail(user1.Id) Client.LoginByEmail(team.Domain, user1.Email, "pwd") channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel) if utils.IsS3Configured() { body := &bytes.Buffer{} writer := multipart.NewWriter(body) part, err := writer.CreateFormFile("files", "test.png") if err != nil { t.Fatal(err) } path := utils.FindDir("web/static/images") file, err := os.Open(path + "/test.png") if err != nil { t.Fatal(err) } defer file.Close() _, err = io.Copy(part, file) if err != nil { t.Fatal(err) } field, err := writer.CreateFormField("channel_id") if err != nil { t.Fatal(err) } _, err = field.Write([]byte(channel1.Id)) if err != nil { t.Fatal(err) } err = writer.Close() if err != nil { t.Fatal(err) } resp, upErr := Client.UploadFile("/files/upload", body.Bytes(), writer.FormDataContentType()) if upErr != nil { t.Fatal(upErr) } filenames := resp.Data.(*model.FileUploadResponse).Filenames // wait a bit for files to ready time.Sleep(5 * time.Second) if _, downErr := Client.GetFile(filenames[0], true); downErr != nil { t.Fatal("file get failed") } team2 := &model.Team{Name: "Name", Domain: "z-z-" + model.NewId() + "a", Email: "*****@*****.**", Type: model.TEAM_OPEN} team2 = Client.Must(Client.CreateTeam(team2)).Data.(*model.Team) user2 := &model.User{TeamId: team2.Id, Email: model.NewId() + "*****@*****.**", FullName: "Corey Hulen", Password: "******"} user2 = Client.Must(Client.CreateUser(user2, "")).Data.(*model.User) Srv.Store.User().VerifyEmail(user2.Id) newProps := make(map[string]string) newProps["filename"] = filenames[0] newProps["time"] = fmt.Sprintf("%v", model.GetMillis()) data := model.MapToJson(newProps) hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) Client.LoginByEmail(team2.Domain, user2.Email, "pwd") if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t="+team.Id, true); downErr != nil { t.Fatal(downErr) } if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash), true); downErr == nil { t.Fatal("Should have errored - missing team id") } if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t=junk", true); downErr == nil { t.Fatal("Should have errored - bad team id") } if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t=12345678901234567890123456", true); downErr == nil { t.Fatal("Should have errored - bad team id") } if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&t="+team.Id, true); downErr == nil { t.Fatal("Should have errored - missing hash") } if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h=junk&t="+team.Id, true); downErr == nil { t.Fatal("Should have errored - bad hash") } if _, downErr := Client.GetFile(filenames[0]+"?h="+url.QueryEscape(hash)+"&t="+team.Id, true); downErr == nil { t.Fatal("Should have errored - missing data") } if _, downErr := Client.GetFile(filenames[0]+"?d=junk&h="+url.QueryEscape(hash)+"&t="+team.Id, true); downErr == nil { t.Fatal("Should have errored - bad data") } if _, downErr := Client.GetFile(filenames[0], true); downErr == nil { t.Fatal("Should have errored - user not logged in and link not public") } var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) fileId := strings.Split(filenames[0], ".")[0] err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filenames[0]) if err != nil { t.Fatal(err) } err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg") if err != nil { t.Fatal(err) } err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.png") if err != nil { t.Fatal(err) } } else { if _, downErr := Client.GetFile("/files/get/yxebdmbz5pgupx7q6ez88rw11a/n3btzxu9hbnapqk36iwaxkjxhc/junk.jpg", false); downErr.StatusCode != http.StatusNotImplemented { t.Fatal("Status code should have been 501 - Not Implemented") } } }
func getFile(c *Context, w http.ResponseWriter, r *http.Request) { if !utils.IsS3Configured() { c.Err = model.NewAppError("getFile", "Unable to get file. Amazon S3 not configured. ", "") 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) var auth aws.Auth auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) 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) asyncGetFile(bucket, path, fileData) if len(hash) > 0 && len(data) > 0 && len(teamId) == 26 { if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) { 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 { var f2 []byte tries := 0 for { time.Sleep(3000 * time.Millisecond) tries++ asyncGetFile(bucket, path, fileData) f2 = <-fileData if f2 != nil { w.Header().Set("Cache-Control", "max-age=2592000, public") w.Header().Set("Content-Length", strconv.Itoa(len(f2))) w.Write(f2) return } else if tries >= 2 { break } } c.Err = model.NewAppError("getFile", "Could not find file.", "url extenstion: "+path) c.Err.StatusCode = http.StatusNotFound return } w.Header().Set("Cache-Control", "max-age=2592000, public") w.Header().Set("Content-Length", strconv.Itoa(len(f))) w.Write(f) }