Merge pull request #205 from mattermost/mm-1351

fixes mm-1351 adds local server storage
Этот коммит содержится в:
Corey Hulen
2015-07-21 15:46:12 -08:00
родитель 06bac01e88 237920e314
Коммит 269185fb0e
16 изменённых файлов: 372 добавлений и 195 удалений

4
.gitignore поставляемый
Просмотреть файл

@@ -47,3 +47,7 @@ web/sass-files/sass/.sass-cache/
*config.codekit *config.codekit
*.sass-cache *.sass-cache
*styles.css *styles.css
# Default local file storage
data/*
api/data/*

Просмотреть файл

@@ -40,7 +40,7 @@ WORKDIR /go
# #
# Install SQL # Install SQL
# #
ENV MYSQL_ROOT_PASSWORD=mostest ENV MYSQL_ROOT_PASSWORD=mostest
ENV MYSQL_USER=mmuser ENV MYSQL_USER=mmuser
@@ -60,7 +60,7 @@ RUN echo "deb http://repo.mysql.com/apt/debian/ wheezy mysql-${MYSQL_MAJOR}" > /
RUN apt-get update \ RUN apt-get update \
&& export DEBIAN_FRONTEND=noninteractive \ && export DEBIAN_FRONTEND=noninteractive \
&& apt-get -y install mysql-server \ && apt-get -y install mysql-server \
&& rm -rf /var/lib/apt/lists/* \ && rm -rf /var/lib/apt/lists/* \
&& rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql && rm -rf /var/lib/mysql && mkdir -p /var/lib/mysql
@@ -88,12 +88,15 @@ ADD . /go/src/github.com/mattermost/platform
ADD ./docker/main.cf /etc/postfix/ ADD ./docker/main.cf /etc/postfix/
RUN go get github.com/tools/godep RUN go get github.com/tools/godep
RUN cd /go/src/github.com/mattermost/platform; godep restore RUN cd /go/src/github.com/mattermost/platform; godep restore
RUN go install github.com/mattermost/platform RUN go install github.com/mattermost/platform
RUN cd /go/src/github.com/mattermost/platform/web/react; npm install RUN cd /go/src/github.com/mattermost/platform/web/react; npm install
RUN chmod +x /go/src/github.com/mattermost/platform/docker/docker-entry.sh RUN chmod +x /go/src/github.com/mattermost/platform/docker/docker-entry.sh
ENTRYPOINT /go/src/github.com/mattermost/platform/docker/docker-entry.sh ENTRYPOINT /go/src/github.com/mattermost/platform/docker/docker-entry.sh
# Create default storage directory
RUN mkdir /mattermost/
# Ports # Ports
EXPOSE 80 EXPOSE 80

Просмотреть файл

@@ -112,6 +112,8 @@ clean:
rm -f web/static/js/bundle*.js rm -f web/static/js/bundle*.js
rm -f web/static/css/styles.css rm -f web/static/css/styles.css
rm -rf data/*
rm -rf api/data/*
rm -rf logs/* rm -rf logs/*

Просмотреть файл

@@ -18,8 +18,10 @@ import (
_ "image/gif" _ "image/gif"
"image/jpeg" "image/jpeg"
"io" "io"
"io/ioutil"
"net/http" "net/http"
"net/url" "net/url"
"os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"strings" "strings"
@@ -27,7 +29,7 @@ import (
) )
func InitFile(r *mux.Router) { func InitFile(r *mux.Router) {
l4g.Debug("Initializing post api routes") l4g.Debug("Initializing file api routes")
sr := r.PathPrefix("/files").Subrouter() sr := r.PathPrefix("/files").Subrouter()
sr.Handle("/upload", ApiUserRequired(uploadFile)).Methods("POST") sr.Handle("/upload", ApiUserRequired(uploadFile)).Methods("POST")
@@ -36,8 +38,8 @@ func InitFile(r *mux.Router) {
} }
func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) { func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.IsS3Configured() { if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
c.Err = model.NewAppError("uploadFile", "Unable to upload file. Amazon S3 not configured. ", "") c.Err = model.NewAppError("uploadFile", "Unable to upload file. Amazon S3 not configured and local server storage turned off. ", "")
c.Err.StatusCode = http.StatusNotImplemented c.Err.StatusCode = http.StatusNotImplemented
return return
} }
@@ -48,13 +50,6 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
return 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 m := r.MultipartForm
props := m.Value props := m.Value
@@ -94,28 +89,25 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
buf := bytes.NewBuffer(nil) buf := bytes.NewBuffer(nil)
io.Copy(buf, file) io.Copy(buf, file)
ext := filepath.Ext(files[i].Filename) filename := filepath.Base(files[i].Filename)
uid := model.NewId() uid := model.NewId()
path := "teams/" + c.Session.TeamId + "/channels/" + channelId + "/users/" + c.Session.UserId + "/" + uid + "/" + files[i].Filename path := "teams/" + c.Session.TeamId + "/channels/" + channelId + "/users/" + c.Session.UserId + "/" + uid + "/" + filename
if model.IsFileExtImage(ext) { if err := writeFile(buf.Bytes(), path); err != nil {
options := s3.Options{} c.Err = err
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 return
} }
fileUrl := "/" + channelId + "/" + c.Session.UserId + "/" + uid + "/" + url.QueryEscape(files[i].Filename) 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) resStruct.Filenames = append(resStruct.Filenames, fileUrl)
} }
@@ -127,13 +119,6 @@ func uploadFile(c *Context, w http.ResponseWriter, r *http.Request) {
func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, channelId, userId string) { func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, channelId, userId string) {
go func() { go func() {
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)
dest := "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" dest := "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/"
for i, filename := range filenames { for i, filename := range filenames {
@@ -169,11 +154,8 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
return return
} }
// Upload thumbnail to S3 if err := writeFile(buf.Bytes(), dest+name+"_thumb.jpg"); err != nil {
options := s3.Options{} l4g.Error("Unable to upload thumbnail channelId=%v userId=%v filename=%v err=%v", channelId, userId, filename, err)
err = bucket.Put(dest+name+"_thumb.jpg", buf.Bytes(), "image/jpeg", s3.Private, options)
if err != nil {
l4g.Error("Unable to upload thumbnail to S3 channelId=%v userId=%v filename=%v err=%v", channelId, userId, filename, err)
return return
} }
}() }()
@@ -188,19 +170,15 @@ func fireAndForgetHandleImages(filenames []string, fileData [][]byte, teamId, ch
} }
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
err = jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90})
//err = png.Encode(buf, preview) err = jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90})
if err != nil { if err != nil {
l4g.Error("Unable to encode image as preview jpg channelId=%v userId=%v filename=%v err=%v", channelId, userId, filename, err) l4g.Error("Unable to encode image as preview jpg channelId=%v userId=%v filename=%v err=%v", channelId, userId, filename, err)
return return
} }
// Upload preview to S3 if err := writeFile(buf.Bytes(), dest+name+"_preview.jpg"); err != nil {
options := s3.Options{} l4g.Error("Unable to upload preview channelId=%v userId=%v filename=%v err=%v", channelId, userId, filename, err)
err = bucket.Put(dest+name+"_preview.jpg", buf.Bytes(), "image/jpeg", s3.Private, options)
if err != nil {
l4g.Error("Unable to upload preview to S3 channelId=%v userId=%v filename=%v err=%v", channelId, userId, filename, err)
return return
} }
}() }()
@@ -215,8 +193,8 @@ type ImageGetResult struct {
} }
func getFile(c *Context, w http.ResponseWriter, r *http.Request) { func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.IsS3Configured() { if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
c.Err = model.NewAppError("getFile", "Unable to get file. Amazon S3 not configured. ", "") c.Err = model.NewAppError("getFile", "Unable to upload file. Amazon S3 not configured and local server storage turned off. ", "")
c.Err.StatusCode = http.StatusNotImplemented c.Err.StatusCode = http.StatusNotImplemented
return return
} }
@@ -247,13 +225,6 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) 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 := "" path := ""
if len(teamId) == 26 { if len(teamId) == 26 {
path = "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + filename path = "teams/" + teamId + "/channels/" + channelId + "/users/" + userId + "/" + filename
@@ -262,7 +233,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
} }
fileData := make(chan []byte) fileData := make(chan []byte)
asyncGetFile(bucket, path, fileData) asyncGetFile(path, fileData)
if len(hash) > 0 && len(data) > 0 && len(teamId) == 26 { if len(hash) > 0 && len(data) > 0 && len(teamId) == 26 {
if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) { if !model.ComparePassword(hash, fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) {
@@ -283,26 +254,7 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
f := <-fileData f := <-fileData
if f == nil { if f == nil {
var f2 []byte c.Err = model.NewAppError("getFile", "Could not find file.", "path="+path)
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 c.Err.StatusCode = http.StatusNotFound
return return
} }
@@ -312,10 +264,11 @@ func getFile(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write(f) w.Write(f)
} }
func asyncGetFile(bucket *s3.Bucket, path string, fileData chan []byte) { func asyncGetFile(path string, fileData chan []byte) {
go func() { go func() {
data, getErr := bucket.Get(path) data, getErr := readFile(path)
if getErr != nil { if getErr != nil {
l4g.Error(getErr)
fileData <- nil fileData <- nil
} else { } else {
fileData <- data fileData <- data
@@ -329,8 +282,8 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
c.Err.StatusCode = http.StatusForbidden c.Err.StatusCode = http.StatusForbidden
} }
if !utils.IsS3Configured() { if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
c.Err = model.NewAppError("getPublicLink", "Unable to get link. Amazon S3 not configured. ", "") c.Err = model.NewAppError("getPublicLink", "Unable to upload file. Amazon S3 not configured and local server storage turned off. ", "")
c.Err.StatusCode = http.StatusNotImplemented c.Err.StatusCode = http.StatusNotImplemented
return return
} }
@@ -344,15 +297,14 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
} }
matches := model.PartialUrlRegex.FindAllStringSubmatch(filename, -1) matches := model.PartialUrlRegex.FindAllStringSubmatch(filename, -1)
if len(matches) == 0 || len(matches[0]) < 5 { if len(matches) == 0 || len(matches[0]) < 4 {
c.SetInvalidParam("getPublicLink", "filename") c.SetInvalidParam("getPublicLink", "filename")
return return
} }
getType := matches[0][1] channelId := matches[0][1]
channelId := matches[0][2] userId := matches[0][2]
userId := matches[0][3] filename = matches[0][3]
filename = matches[0][4]
cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId) cchan := Srv.Store.Channel().CheckPermissionsTo(c.Session.TeamId, channelId, c.Session.UserId)
@@ -363,7 +315,7 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
data := model.MapToJson(newProps) data := model.MapToJson(newProps)
hash := model.HashPassword(fmt.Sprintf("%v:%v", data, utils.Cfg.ServiceSettings.PublicLinkSalt)) 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.GetSiteURL(), getType, channelId, userId, filename, url.QueryEscape(data), url.QueryEscape(hash), c.Session.TeamId) 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)
if !c.HasPermissionsToChannel(cchan, "getPublicLink") { if !c.HasPermissionsToChannel(cchan, "getPublicLink") {
return return
@@ -374,3 +326,78 @@ func getPublicLink(c *Context, w http.ResponseWriter, r *http.Request) {
w.Write([]byte(model.MapToJson(rData))) w.Write([]byte(model.MapToJson(rData)))
} }
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 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.", "")
}
}

Просмотреть файл

@@ -38,7 +38,7 @@ func TestUploadFile(t *testing.T) {
body := &bytes.Buffer{} body := &bytes.Buffer{}
writer := multipart.NewWriter(body) writer := multipart.NewWriter(body)
part, err := writer.CreateFormFile("files", "test.png") part, err := writer.CreateFormFile("files", "../test.png")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -68,12 +68,17 @@ func TestUploadFile(t *testing.T) {
} }
resp, appErr := Client.UploadFile("/files/upload", body.Bytes(), writer.FormDataContentType()) resp, appErr := Client.UploadFile("/files/upload", body.Bytes(), writer.FormDataContentType())
if utils.IsS3Configured() { if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
if appErr != nil { if appErr != nil {
t.Fatal(appErr) t.Fatal(appErr)
} }
filenames := resp.Data.(*model.FileUploadResponse).Filenames filenames := strings.Split(resp.Data.(*model.FileUploadResponse).Filenames[0], "/")
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
if strings.Contains(filename, "../") {
t.Fatal("relative path should have been sanitized out")
}
fileId := strings.Split(filename, ".")[0]
var auth aws.Auth var auth aws.Auth
auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId
@@ -82,12 +87,10 @@ func TestUploadFile(t *testing.T) {
s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region])
bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket)
fileId := strings.Split(filenames[0], ".")[0]
// wait a bit for files to ready // wait a bit for files to ready
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filenames[0]) err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -97,13 +100,38 @@ func TestUploadFile(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.png") err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
} else if utils.Cfg.ServiceSettings.UseLocalStorage && len(utils.Cfg.ServiceSettings.StorageDirectory) > 0 {
filenames := strings.Split(resp.Data.(*model.FileUploadResponse).Filenames[0], "/")
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
if strings.Contains(filename, "../") {
t.Fatal("relative path should have been sanitized out")
}
fileId := strings.Split(filename, ".")[0]
// wait a bit for files to ready
time.Sleep(5 * time.Second)
path := utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
path = utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
path = utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
} else { } else {
if appErr == nil { if appErr == nil {
t.Fatal("S3 not configured, should have failed") t.Fatal("S3 and local storage not configured, should have failed")
} }
} }
} }
@@ -123,7 +151,7 @@ func TestGetFile(t *testing.T) {
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} 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) channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
if utils.IsS3Configured() { if utils.IsS3Configured() || utils.Cfg.ServiceSettings.UseLocalStorage {
body := &bytes.Buffer{} body := &bytes.Buffer{}
writer := multipart.NewWriter(body) writer := multipart.NewWriter(body)
@@ -169,8 +197,8 @@ func TestGetFile(t *testing.T) {
// wait a bit for files to ready // wait a bit for files to ready
time.Sleep(5 * time.Second) time.Sleep(5 * time.Second)
if _, downErr := Client.GetFile(filenames[0], true); downErr != nil { if _, downErr := Client.GetFile(filenames[0], false); downErr != nil {
t.Fatal("file get failed") t.Fatal(downErr)
} }
team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN} team2 := &model.Team{DisplayName: "Name", Name: "z-z-" + model.NewId() + "a", Email: "test@nowhere.com", Type: model.TEAM_OPEN}
@@ -189,35 +217,35 @@ func TestGetFile(t *testing.T) {
Client.LoginByEmail(team2.Name, user2.Email, "pwd") Client.LoginByEmail(team2.Name, user2.Email, "pwd")
if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t="+team.Id, true); downErr != nil { if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t="+team.Id, false); downErr != nil {
t.Fatal(downErr) t.Fatal(downErr)
} }
if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash), true); downErr == nil { if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash), false); downErr == nil {
t.Fatal("Should have errored - missing team id") 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 { if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t=junk", false); downErr == nil {
t.Fatal("Should have errored - bad team id") 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 { if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h="+url.QueryEscape(hash)+"&t=12345678901234567890123456", false); downErr == nil {
t.Fatal("Should have errored - bad team id") t.Fatal("Should have errored - bad team id")
} }
if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&t="+team.Id, true); downErr == nil { if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&t="+team.Id, false); downErr == nil {
t.Fatal("Should have errored - missing hash") 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 { if _, downErr := Client.GetFile(filenames[0]+"?d="+url.QueryEscape(data)+"&h=junk&t="+team.Id, false); downErr == nil {
t.Fatal("Should have errored - bad hash") t.Fatal("Should have errored - bad hash")
} }
if _, downErr := Client.GetFile(filenames[0]+"?h="+url.QueryEscape(hash)+"&t="+team.Id, true); downErr == nil { if _, downErr := Client.GetFile(filenames[0]+"?h="+url.QueryEscape(hash)+"&t="+team.Id, false); downErr == nil {
t.Fatal("Should have errored - missing data") 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 { if _, downErr := Client.GetFile(filenames[0]+"?d=junk&h="+url.QueryEscape(hash)+"&t="+team.Id, false); downErr == nil {
t.Fatal("Should have errored - bad data") t.Fatal("Should have errored - bad data")
} }
@@ -225,28 +253,51 @@ func TestGetFile(t *testing.T) {
t.Fatal("Should have errored - user not logged in and link not public") t.Fatal("Should have errored - user not logged in and link not public")
} }
var auth aws.Auth if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId var auth aws.Auth
auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId
auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey
s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region])
bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket)
fileId := strings.Split(filenames[0], ".")[0] filenames := strings.Split(resp.Data.(*model.FileUploadResponse).Filenames[0], "/")
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
fileId := strings.Split(filename, ".")[0]
err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filenames[0]) err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg") err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.png") err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg")
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
}
} else {
filenames := strings.Split(resp.Data.(*model.FileUploadResponse).Filenames[0], "/")
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
fileId := strings.Split(filename, ".")[0]
path := utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
path = utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
path = utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
} }
} else { } else {
if _, downErr := Client.GetFile("/files/get/yxebdmbz5pgupx7q6ez88rw11a/n3btzxu9hbnapqk36iwaxkjxhc/junk.jpg", false); downErr.StatusCode != http.StatusNotImplemented { if _, downErr := Client.GetFile("/files/get/yxebdmbz5pgupx7q6ez88rw11a/n3btzxu9hbnapqk36iwaxkjxhc/junk.jpg", false); downErr.StatusCode != http.StatusNotImplemented {
@@ -274,7 +325,7 @@ func TestGetPublicLink(t *testing.T) {
channel1 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} 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) channel1 = Client.Must(Client.CreateChannel(channel1)).Data.(*model.Channel)
if utils.IsS3Configured() { if utils.IsS3Configured() || utils.Cfg.ServiceSettings.UseLocalStorage {
body := &bytes.Buffer{} body := &bytes.Buffer{}
writer := multipart.NewWriter(body) writer := multipart.NewWriter(body)
@@ -350,26 +401,52 @@ func TestGetPublicLink(t *testing.T) {
t.Fatal("should have errored, user not member of channel") t.Fatal("should have errored, user not member of channel")
} }
// perform clean-up on s3 if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
var auth aws.Auth // perform clean-up on s3
auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId var auth aws.Auth
auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId
auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey
s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region])
bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket)
fileId := strings.Split(filenames[0], ".")[0] filenames := strings.Split(resp.Data.(*model.FileUploadResponse).Filenames[0], "/")
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
fileId := strings.Split(filename, ".")[0]
if err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + rpost1.Data.(*model.Post).UserId + "/" + filenames[0]); err != nil { err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename)
t.Fatal(err) if 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 { err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg")
t.Fatal(err) if 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 { err = bucket.Del("teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg")
t.Fatal(err) if err != nil {
t.Fatal(err)
}
} else {
filenames := strings.Split(resp.Data.(*model.FileUploadResponse).Filenames[0], "/")
filename := filenames[len(filenames)-2] + "/" + filenames[len(filenames)-1]
fileId := strings.Split(filename, ".")[0]
path := utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + filename
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
path = utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_thumb.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
path = utils.Cfg.ServiceSettings.StorageDirectory + "teams/" + team.Id + "/channels/" + channel1.Id + "/users/" + user1.Id + "/" + fileId + "_preview.jpg"
if err := os.Remove(path); err != nil {
t.Fatal("Couldn't remove file at " + path)
}
} }
} else { } else {
data := make(map[string]string) data := make(map[string]string)

Просмотреть файл

@@ -160,6 +160,39 @@ func CreatePost(c *Context, post *model.Post, doUpdateLastViewed bool) (*model.P
post.UserId = c.Session.UserId post.UserId = c.Session.UserId
if len(post.Filenames) > 0 {
doRemove := false
for i := len(post.Filenames) - 1; i >= 0; i-- {
path := post.Filenames[i]
doRemove = false
if model.UrlRegex.MatchString(path) {
continue
} else if model.PartialUrlRegex.MatchString(path) {
matches := model.PartialUrlRegex.FindAllStringSubmatch(path, -1)
if len(matches) == 0 || len(matches[0]) < 4 {
doRemove = true
}
channelId := matches[0][1]
if channelId != post.ChannelId {
doRemove = true
}
userId := matches[0][2]
if userId != post.UserId {
doRemove = true
}
} else {
doRemove = true
}
if doRemove {
l4g.Error("Bad filename discarded, filename=%v", path)
post.Filenames = append(post.Filenames[:i], post.Filenames[i+1:]...)
}
}
}
var rpost *model.Post var rpost *model.Post
if result := <-Srv.Store.Post().Save(post); result.Err != nil { if result := <-Srv.Store.Post().Save(post); result.Err != nil {
return nil, result.Err return nil, result.Err

Просмотреть файл

@@ -37,7 +37,7 @@ func TestCreatePost(t *testing.T) {
channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id} channel2 := &model.Channel{DisplayName: "Test API Name", Name: "a" + model.NewId() + "a", Type: model.CHANNEL_OPEN, TeamId: team.Id}
channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel) channel2 = Client.Must(Client.CreateChannel(channel2)).Data.(*model.Channel)
filenames := []string{"/api/v1/files/get/12345678901234567890123456/12345678901234567890123456/test.png", "/api/v1/files/get/" + channel1.Id + "/" + user1.Id + "/test.png"} filenames := []string{"/12345678901234567890123456/12345678901234567890123456/12345678901234567890123456/test.png", "/" + channel1.Id + "/" + user1.Id + "/test.png", "www.mattermost.com/fake/url", "junk"}
post1 := &model.Post{ChannelId: channel1.Id, Message: "#hashtag a" + model.NewId() + "a", Filenames: filenames} post1 := &model.Post{ChannelId: channel1.Id, Message: "#hashtag a" + model.NewId() + "a", Filenames: filenames}
rpost1, err := Client.CreatePost(post1) rpost1, err := Client.CreatePost(post1)

Просмотреть файл

@@ -7,8 +7,6 @@ import (
"bytes" "bytes"
l4g "code.google.com/p/log4go" l4g "code.google.com/p/log4go"
"fmt" "fmt"
"github.com/goamz/goamz/aws"
"github.com/goamz/goamz/s3"
"github.com/gorilla/mux" "github.com/gorilla/mux"
"github.com/mattermost/platform/model" "github.com/mattermost/platform/model"
"github.com/mattermost/platform/store" "github.com/mattermost/platform/store"
@@ -598,7 +596,7 @@ func createProfileImage(username string, userId string) ([]byte, *model.AppError
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
if imgErr := png.Encode(buf, img); imgErr != nil { if imgErr := png.Encode(buf, img); imgErr != nil {
return nil, model.NewAppError("getProfileImage", "Could not encode default profile image", imgErr.Error()) return nil, model.NewAppError("createProfileImage", "Could not encode default profile image", imgErr.Error())
} else { } else {
return buf.Bytes(), nil return buf.Bytes(), nil
} }
@@ -613,34 +611,25 @@ func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
return return
} else { } else {
var img []byte var img []byte
var err *model.AppError
if !utils.IsS3Configured() { if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
img, err = createProfileImage(result.Data.(*model.User).Username, id) var err *model.AppError
if err != nil { if img, err = createProfileImage(result.Data.(*model.User).Username, id); err != nil {
c.Err = err c.Err = err
return return
} }
} else { } 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" path := "teams/" + c.Session.TeamId + "/users/" + id + "/profile.png"
if data, getErr := bucket.Get(path); getErr != nil { if data, err := readFile(path); err != nil {
img, err = createProfileImage(result.Data.(*model.User).Username, id)
if err != nil { if img, err = createProfileImage(result.Data.(*model.User).Username, id); err != nil {
c.Err = err c.Err = err
return return
} }
options := s3.Options{} if err := writeFile(img, path); err != nil {
if err := bucket.Put(path, img, "image", s3.Private, options); err != nil { c.Err = err
c.Err = model.NewAppError("getImage", "Couldn't upload default profile image", err.Error())
return return
} }
@@ -660,8 +649,8 @@ func getProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
} }
func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) { func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
if !utils.IsS3Configured() { if !utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
c.Err = model.NewAppError("uploadProfileImage", "Unable to upload image. Amazon S3 not configured. ", "") c.Err = model.NewAppError("uploadProfileImage", "Unable to upload file. Amazon S3 not configured and local server storage turned off. ", "")
c.Err.StatusCode = http.StatusNotImplemented c.Err.StatusCode = http.StatusNotImplemented
return return
} }
@@ -671,13 +660,6 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
return 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 m := r.MultipartForm
imageArray, ok := m.File["image"] imageArray, ok := m.File["image"]
@@ -721,8 +703,7 @@ func uploadProfileImage(c *Context, w http.ResponseWriter, r *http.Request) {
path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png" path := "teams/" + c.Session.TeamId + "/users/" + c.Session.UserId + "/profile.png"
options := s3.Options{} if err := writeFile(buf.Bytes(), path); err != nil {
if err := bucket.Put(path, buf.Bytes(), "image", s3.Private, options); err != nil {
c.Err = model.NewAppError("uploadProfileImage", "Couldn't upload profile image", "") c.Err = model.NewAppError("uploadProfileImage", "Couldn't upload profile image", "")
return return
} }

Просмотреть файл

@@ -356,6 +356,24 @@ func TestUserCreateImage(t *testing.T) {
Client.DoGet("/users/"+user.Id+"/image", "", "") 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 TestUserUploadProfileImage(t *testing.T) { func TestUserUploadProfileImage(t *testing.T) {
@@ -368,7 +386,7 @@ func TestUserUploadProfileImage(t *testing.T) {
user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User) user = Client.Must(Client.CreateUser(user, "")).Data.(*model.User)
store.Must(Srv.Store.User().VerifyEmail(user.Id)) store.Must(Srv.Store.User().VerifyEmail(user.Id))
if utils.IsS3Configured() { if utils.IsS3Configured() || utils.Cfg.ServiceSettings.UseLocalStorage {
body := &bytes.Buffer{} body := &bytes.Buffer{}
writer := multipart.NewWriter(body) writer := multipart.NewWriter(body)
@@ -436,15 +454,22 @@ func TestUserUploadProfileImage(t *testing.T) {
Client.DoGet("/users/"+user.Id+"/image", "", "") Client.DoGet("/users/"+user.Id+"/image", "", "")
var auth aws.Auth if utils.IsS3Configured() && !utils.Cfg.ServiceSettings.UseLocalStorage {
auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId var auth aws.Auth
auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey auth.AccessKey = utils.Cfg.AWSSettings.S3AccessKeyId
auth.SecretKey = utils.Cfg.AWSSettings.S3SecretAccessKey
s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region]) s := s3.New(auth, aws.Regions[utils.Cfg.AWSSettings.S3Region])
bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket) bucket := s.Bucket(utils.Cfg.AWSSettings.S3Bucket)
if err := bucket.Del("teams/" + user.TeamId + "/users/" + user.Id + "/profile.png"); err != nil { if err := bucket.Del("teams/" + user.TeamId + "/users/" + user.Id + "/profile.png"); err != nil {
t.Fatal(err) 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 { } else {
body := &bytes.Buffer{} body := &bytes.Buffer{}

Просмотреть файл

@@ -19,7 +19,9 @@
"InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6", "InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6",
"PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4", "PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4",
"ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t", "ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
"AnalyticsUrl": "" "AnalyticsUrl": "",
"UseLocalStorage": true,
"StorageDirectory": "./data/"
}, },
"SqlSettings": { "SqlSettings": {
"DriverName": "mysql", "DriverName": "mysql",

Просмотреть файл

@@ -19,7 +19,9 @@
"InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6", "InviteSalt": "gxHVDcKUyP2y1eiyW8S8na1UYQAfq6J6",
"PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4", "PublicLinkSalt": "TO3pTyXIZzwHiwyZgGql7lM7DG3zeId4",
"ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t", "ResetSalt": "IPxFzSfnDFsNsRafZxz8NaYqFKhf9y2t",
"AnalyticsUrl": "" "AnalyticsUrl": "",
"UseLocalStorage": true,
"StorageDirectory": "/mattermost/data/"
}, },
"SqlSettings": { "SqlSettings": {
"DriverName": "mysql", "DriverName": "mysql",

Просмотреть файл

@@ -550,7 +550,7 @@ func (c *Client) GetFile(url string, isFullUrl bool) (*Result, *AppError) {
if isFullUrl { if isFullUrl {
rq, _ = http.NewRequest("GET", url, nil) rq, _ = http.NewRequest("GET", url, nil)
} else { } else {
rq, _ = http.NewRequest("GET", c.Url+url, nil) rq, _ = http.NewRequest("GET", c.Url+"/files/get"+url, nil)
} }
if len(c.AuthToken) > 0 { if len(c.AuthToken) > 0 {

Просмотреть файл

@@ -319,6 +319,6 @@ func ClearMentionTags(post string) string {
} }
var UrlRegex = regexp.MustCompile(`^((?:[a-z]+:\/\/)?(?:(?:[a-z0-9\-]+\.)+(?:[a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(?:\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(?:\?[a-z0-9+_~\-\.%=&amp;]*)?)?(?:#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(?:\s+|$)$`) var UrlRegex = regexp.MustCompile(`^((?:[a-z]+:\/\/)?(?:(?:[a-z0-9\-]+\.)+(?:[a-z]{2}|aero|arpa|biz|com|coop|edu|gov|info|int|jobs|mil|museum|name|nato|net|org|pro|travel|local|internal))(:[0-9]{1,5})?(?:\/[a-z0-9_\-\.~]+)*(\/([a-z0-9_\-\.]*)(?:\?[a-z0-9+_~\-\.%=&amp;]*)?)?(?:#[a-zA-Z0-9!$&'()*+.=-_~:@/?]*)?)(?:\s+|$)$`)
var PartialUrlRegex = regexp.MustCompile(`/api/v1/files/(get|get_image)/([A-Za-z0-9]{26})/([A-Za-z0-9]{26})/(([A-Za-z0-9]+/)?.+\.[A-Za-z0-9]{3,})`) var PartialUrlRegex = regexp.MustCompile(`/([A-Za-z0-9]{26})/([A-Za-z0-9]{26})/((?:[A-Za-z0-9]{26})?.+\.[A-Za-z0-9]{3,})`)
var SplitRunes = map[rune]bool{',': true, ' ': true, '.': true, '!': true, '?': true, ':': true, ';': true, '\n': true, '<': true, '>': true, '(': true, ')': true, '{': true, '}': true, '[': true, ']': true, '+': true, '/': true, '\\': true} var SplitRunes = map[rune]bool{',': true, ' ': true, '.': true, '!': true, '?': true, ':': true, ';': true, '\n': true, '<': true, '>': true, '(': true, ')': true, '{': true, '}': true, '[': true, ']': true, '+': true, '/': true, '\\': true}

Просмотреть файл

@@ -18,16 +18,18 @@ const (
) )
type ServiceSettings struct { type ServiceSettings struct {
SiteName string SiteName string
Mode string Mode string
AllowTesting bool AllowTesting bool
UseSSL bool UseSSL bool
Port string Port string
Version string Version string
InviteSalt string InviteSalt string
PublicLinkSalt string PublicLinkSalt string
ResetSalt string ResetSalt string
AnalyticsUrl string AnalyticsUrl string
UseLocalStorage bool
StorageDirectory string
} }
type SqlSettings struct { type SqlSettings struct {

19
utils/urlencode.go Обычный файл
Просмотреть файл

@@ -0,0 +1,19 @@
// Copyright (c) 2015 Spinpunch, Inc. All Rights Reserved.
// See License.txt for license information.
package utils
import (
"net/url"
"strings"
)
func UrlEncode(str string) string {
strs := strings.Split(str, " ")
for i, s := range strs {
strs[i] = url.QueryEscape(s)
}
return strings.Join(strs, "%20")
}

Просмотреть файл

@@ -155,7 +155,7 @@ module.exports = React.createClass({
var imgClass = "hidden"; var imgClass = "hidden";
if (this.state.loaded[id] && this.state.imgId == id) imgClass = ""; if (this.state.loaded[id] && this.state.imgId == id) imgClass = "";
img[info['path']] = <a key={info['path']} className={imgClass} href={this.props.filenames[id]} target="_blank"><img ref="image" src={preview_filename}/></a>; img[info['path']] = <a key={info['path']} className={imgClass} href={info.path+"."+info.ext} target="_blank"><img ref="image" src={preview_filename}/></a>;
} }
} }