server/public/ -- pre-requisite changes (#23278)
* invert depdendency: filestore -> model
* markdown: nolint:misspell
* inline jsonutils within model
* push model.GetInfoForBytes -> channels/app
* push channel/utils.CompileGo* -> plugin/utils
* push plugin/scheduler -> channels/jobs/plugins
* push utils.Copy(File|Dir) -> model
* oauthproiders/gitlab -> channels/app/oauthproviders/gitlab
* decouple plugin from einterfaces.MetricsInterface
* fix TestGetInfoForFile
* Revert "Run golangci in server CI (#23240)"
This reverts commit 349e5d4573.
* add model/utils
---------
Co-authored-by: Agniva De Sarker <agnivade@yahoo.co.in>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
5c28071fb3
Коммит
f28a2bcca7
@@ -26,7 +26,7 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mail"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mail"
|
||||||
|
|
||||||
_ "github.com/mattermost/mattermost-server/server/v8/model/oauthproviders/gitlab"
|
_ "github.com/mattermost/mattermost-server/server/v8/channels/app/oauthproviders/gitlab"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateUser(t *testing.T) {
|
func TestCreateUser(t *testing.T) {
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ func (a *App) UploadEmojiImage(c request.CTX, id string, imageData *multipart.Fi
|
|||||||
if config.Width > MaxEmojiWidth || config.Height > MaxEmojiHeight {
|
if config.Width > MaxEmojiWidth || config.Height > MaxEmojiHeight {
|
||||||
data := buf.Bytes()
|
data := buf.Bytes()
|
||||||
newbuf := bytes.NewBuffer(nil)
|
newbuf := bytes.NewBuffer(nil)
|
||||||
info, err := model.GetInfoForBytes(imageData.Filename, bytes.NewReader(data), len(data))
|
info, err := getInfoForBytes(imageData.Filename, bytes.NewReader(data), len(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ func (a *App) FileBackend() filestore.FileBackend {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (a *App) CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError {
|
func (a *App) CheckMandatoryS3Fields(settings *model.FileSettings) *model.AppError {
|
||||||
fileBackendSettings := settings.ToFileBackendSettings(false, false)
|
fileBackendSettings := filestore.NewFileBackendSettingsFromConfig(settings, false, false)
|
||||||
err := fileBackendSettings.CheckMandatoryS3Fields()
|
err := fileBackendSettings.CheckMandatoryS3Fields()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.NewAppError("CheckMandatoryS3Fields", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest).Wrap(err)
|
return model.NewAppError("CheckMandatoryS3Fields", "api.admin.test_s3.missing_s3_bucket", nil, "", http.StatusBadRequest).Wrap(err)
|
||||||
@@ -96,7 +96,7 @@ func (a *App) TestFileStoreConnection() *model.AppError {
|
|||||||
func (a *App) TestFileStoreConnectionWithConfig(cfg *model.FileSettings) *model.AppError {
|
func (a *App) TestFileStoreConnectionWithConfig(cfg *model.FileSettings) *model.AppError {
|
||||||
license := a.Srv().License()
|
license := a.Srv().License()
|
||||||
insecure := a.Config().ServiceSettings.EnableInsecureOutgoingConnections
|
insecure := a.Config().ServiceSettings.EnableInsecureOutgoingConnections
|
||||||
backend, err := filestore.NewFileBackend(cfg.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure))
|
backend, err := filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(cfg, license != nil && *license.Features.Compliance, insecure != nil && *insecure))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return model.NewAppError("FileBackend", "api.file.no_driver.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
return model.NewAppError("FileBackend", "api.file.no_driver.app_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||||
}
|
}
|
||||||
@@ -260,7 +260,7 @@ func (a *App) getInfoForFilename(post *model.Post, teamID, channelID, userID, ol
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := model.GetInfoForBytes(name, bytes.NewReader(data), len(data))
|
info, err := getInfoForBytes(name, bytes.NewReader(data), len(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
mlog.Warn(
|
mlog.Warn(
|
||||||
"Unable to fully decode file info when migrating post to use FileInfos",
|
"Unable to fully decode file info when migrating post to use FileInfos",
|
||||||
@@ -879,7 +879,7 @@ func (a *App) DoUploadFileExpectModification(c request.CTX, now time.Time, rawTe
|
|||||||
channelID := filepath.Base(rawChannelId)
|
channelID := filepath.Base(rawChannelId)
|
||||||
userID := filepath.Base(rawUserId)
|
userID := filepath.Base(rawUserId)
|
||||||
|
|
||||||
info, err := model.GetInfoForBytes(filename, bytes.NewReader(data), len(data))
|
info, err := getInfoForBytes(filename, bytes.NewReader(data), len(data))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err.StatusCode = http.StatusBadRequest
|
err.StatusCode = http.StatusBadRequest
|
||||||
return nil, data, err
|
return nil, data, err
|
||||||
|
|||||||
58
server/channels/app/file_info.go
Обычный файл
58
server/channels/app/file_info.go
Обычный файл
@@ -0,0 +1,58 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"image"
|
||||||
|
"io"
|
||||||
|
"mime"
|
||||||
|
"net/http"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/channels/utils/imgutils"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getInfoForBytes(name string, data io.ReadSeeker, size int) (*model.FileInfo, *model.AppError) {
|
||||||
|
info := &model.FileInfo{
|
||||||
|
Name: name,
|
||||||
|
Size: int64(size),
|
||||||
|
}
|
||||||
|
var err *model.AppError
|
||||||
|
|
||||||
|
extension := strings.ToLower(filepath.Ext(name))
|
||||||
|
info.MimeType = mime.TypeByExtension(extension)
|
||||||
|
|
||||||
|
if extension != "" {
|
||||||
|
// The client expects a file extension without the leading period
|
||||||
|
info.Extension = extension[1:]
|
||||||
|
} else {
|
||||||
|
info.Extension = extension
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.IsImage() {
|
||||||
|
// Only set the width and height if it's actually an image that we can understand
|
||||||
|
if config, _, err := image.DecodeConfig(data); err == nil {
|
||||||
|
info.Width = config.Width
|
||||||
|
info.Height = config.Height
|
||||||
|
|
||||||
|
if info.MimeType == "image/gif" {
|
||||||
|
// Just show the gif itself instead of a preview image for animated gifs
|
||||||
|
data.Seek(0, io.SeekStart)
|
||||||
|
frameCount, err := imgutils.CountGIFFrames(data)
|
||||||
|
if err != nil {
|
||||||
|
// Still return the rest of the info even though it doesn't appear to be an actual gif
|
||||||
|
info.HasPreviewImage = true
|
||||||
|
return info, model.NewAppError("getInfoForBytes", "app.file_info.get.gif.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||||
|
}
|
||||||
|
info.HasPreviewImage = frameCount == 1
|
||||||
|
} else {
|
||||||
|
info.HasPreviewImage = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
148
server/channels/app/file_info_test.go
Обычный файл
148
server/channels/app/file_info_test.go
Обычный файл
@@ -0,0 +1,148 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetInfoForFile(t *testing.T) {
|
||||||
|
fakeFile := make([]byte, 1000)
|
||||||
|
|
||||||
|
pngFile, err := os.ReadFile("tests/test.png")
|
||||||
|
require.NoError(t, err, "Failed to load test.png")
|
||||||
|
|
||||||
|
// base 64 encoded version of handtinywhite.gif from http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
|
||||||
|
gifFile, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=")
|
||||||
|
|
||||||
|
animatedGifFile, err := os.ReadFile("tests/testgif.gif")
|
||||||
|
require.NoError(t, err, "Failed to load testgif.gif")
|
||||||
|
|
||||||
|
var ttc = []struct {
|
||||||
|
testName string
|
||||||
|
filename string
|
||||||
|
file []byte
|
||||||
|
usePrefixForMime bool
|
||||||
|
expectedExtension string
|
||||||
|
expectedSize int
|
||||||
|
expectedMime string
|
||||||
|
expectedWidth int
|
||||||
|
expectedHeight int
|
||||||
|
expectedHasPreviewImage bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
testName: "Text File",
|
||||||
|
filename: "file.txt",
|
||||||
|
file: fakeFile,
|
||||||
|
usePrefixForMime: true,
|
||||||
|
expectedExtension: "txt",
|
||||||
|
expectedSize: 1000,
|
||||||
|
expectedMime: "text/plain",
|
||||||
|
expectedWidth: 0,
|
||||||
|
expectedHeight: 0,
|
||||||
|
expectedHasPreviewImage: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "PNG file",
|
||||||
|
filename: "test.png",
|
||||||
|
file: pngFile,
|
||||||
|
usePrefixForMime: false,
|
||||||
|
expectedExtension: "png",
|
||||||
|
expectedSize: 279591,
|
||||||
|
expectedMime: "image/png",
|
||||||
|
expectedWidth: 408,
|
||||||
|
expectedHeight: 336,
|
||||||
|
expectedHasPreviewImage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Static Gif File",
|
||||||
|
filename: "handtinywhite.gif",
|
||||||
|
file: gifFile,
|
||||||
|
usePrefixForMime: false,
|
||||||
|
expectedExtension: "gif",
|
||||||
|
expectedSize: 35,
|
||||||
|
expectedMime: "image/gif",
|
||||||
|
expectedWidth: 1,
|
||||||
|
expectedHeight: 1,
|
||||||
|
expectedHasPreviewImage: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "Animated Gif File",
|
||||||
|
filename: "testgif.gif",
|
||||||
|
file: animatedGifFile,
|
||||||
|
usePrefixForMime: false,
|
||||||
|
expectedExtension: "gif",
|
||||||
|
expectedSize: 38689,
|
||||||
|
expectedMime: "image/gif",
|
||||||
|
expectedWidth: 118,
|
||||||
|
expectedHeight: 118,
|
||||||
|
expectedHasPreviewImage: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
testName: "No extension File",
|
||||||
|
filename: "filewithoutextension",
|
||||||
|
file: fakeFile,
|
||||||
|
usePrefixForMime: false,
|
||||||
|
expectedExtension: "",
|
||||||
|
expectedSize: 1000,
|
||||||
|
expectedMime: "",
|
||||||
|
expectedWidth: 0,
|
||||||
|
expectedHeight: 0,
|
||||||
|
expectedHasPreviewImage: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Always make the extension lower case to make it easier to use in other places
|
||||||
|
testName: "Uppercase extension File",
|
||||||
|
filename: "file.TXT",
|
||||||
|
file: fakeFile,
|
||||||
|
usePrefixForMime: true,
|
||||||
|
expectedExtension: "txt",
|
||||||
|
expectedSize: 1000,
|
||||||
|
expectedMime: "text/plain",
|
||||||
|
expectedWidth: 0,
|
||||||
|
expectedHeight: 0,
|
||||||
|
expectedHasPreviewImage: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Don't error out for image formats we don't support
|
||||||
|
testName: "Not supported File",
|
||||||
|
filename: "file.tif",
|
||||||
|
file: fakeFile,
|
||||||
|
usePrefixForMime: false,
|
||||||
|
expectedExtension: "tif",
|
||||||
|
expectedSize: 1000,
|
||||||
|
expectedMime: "image/tiff",
|
||||||
|
expectedWidth: 0,
|
||||||
|
expectedHeight: 0,
|
||||||
|
expectedHasPreviewImage: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range ttc {
|
||||||
|
t.Run(tc.testName, func(t *testing.T) {
|
||||||
|
info, appErr := getInfoForBytes(tc.filename, bytes.NewReader(tc.file), len(tc.file))
|
||||||
|
require.Nil(t, appErr)
|
||||||
|
|
||||||
|
assert.Equalf(t, tc.filename, info.Name, "Got incorrect filename: %v", info.Name)
|
||||||
|
assert.Equalf(t, tc.expectedExtension, info.Extension, "Got incorrect extension: %v", info.Extension)
|
||||||
|
assert.EqualValuesf(t, tc.expectedSize, info.Size, "Got incorrect size: %v", info.Size)
|
||||||
|
assert.Equalf(t, tc.expectedWidth, info.Width, "Got incorrect width: %v", info.Width)
|
||||||
|
assert.Equalf(t, tc.expectedHeight, info.Height, "Got incorrect height: %v", info.Height)
|
||||||
|
assert.Equalf(t, tc.expectedHasPreviewImage, info.HasPreviewImage, "Got incorrect has preview image: %v", info.HasPreviewImage)
|
||||||
|
|
||||||
|
if tc.usePrefixForMime {
|
||||||
|
assert.Truef(t, strings.HasPrefix(info.MimeType, tc.expectedMime), "Got incorrect mime type: %v", info.MimeType)
|
||||||
|
} else {
|
||||||
|
assert.Equalf(t, tc.expectedMime, info.MimeType, "Got incorrect mime type: %v", info.MimeType)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -225,7 +225,7 @@ func New(sc ServiceConfig, options ...Option) (*PlatformService, error) {
|
|||||||
// Step 3: Initialize filestore
|
// Step 3: Initialize filestore
|
||||||
if ps.filestore == nil {
|
if ps.filestore == nil {
|
||||||
insecure := ps.Config().ServiceSettings.EnableInsecureOutgoingConnections
|
insecure := ps.Config().ServiceSettings.EnableInsecureOutgoingConnections
|
||||||
backend, err2 := filestore.NewFileBackend(ps.Config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure))
|
backend, err2 := filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(&ps.Config().FileSettings, license != nil && *license.Features.Compliance, insecure != nil && *insecure))
|
||||||
if err2 != nil {
|
if err2 != nil {
|
||||||
return nil, fmt.Errorf("failed to initialize filebackend: %w", err2)
|
return nil, fmt.Errorf("failed to initialize filebackend: %w", err2)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,11 +27,11 @@ import (
|
|||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces/mocks"
|
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces/mocks"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils/fileutils"
|
"github.com/mattermost/mattermost-server/server/v8/channels/utils/fileutils"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/i18n"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/i18n"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/plugin"
|
"github.com/mattermost/mattermost-server/server/v8/plugin"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/plugin/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getDefaultPluginSettingsSchema() string {
|
func getDefaultPluginSettingsSchema() string {
|
||||||
|
|||||||
@@ -21,10 +21,10 @@ import (
|
|||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces/mocks"
|
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces/mocks"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/plugin"
|
"github.com/mattermost/mattermost-server/server/v8/plugin"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/plugin/plugintest"
|
"github.com/mattermost/mattermost-server/server/v8/plugin/plugintest"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/plugin/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, apiFunc func(*model.Manifest) plugin.API) (func(), []string, []error) {
|
func SetAppEnvironmentWithPlugins(t *testing.T, pluginCode []string, app *App, apiFunc func(*model.Manifest) plugin.API) (func(), []string, []error) {
|
||||||
|
|||||||
@@ -45,8 +45,8 @@ import (
|
|||||||
|
|
||||||
"github.com/blang/semver"
|
"github.com/blang/semver"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/model/utils"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/filestore"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/filestore"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/plugin"
|
"github.com/mattermost/mattermost-server/server/v8/plugin"
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/last_accessible_post"
|
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/last_accessible_post"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/migrations"
|
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/migrations"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/notify_admin"
|
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/notify_admin"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/plugins"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/product_notices"
|
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/product_notices"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/resend_invitation_email"
|
"github.com/mattermost/mattermost-server/server/v8/channels/jobs/resend_invitation_email"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/product"
|
"github.com/mattermost/mattermost-server/server/v8/channels/product"
|
||||||
@@ -70,7 +71,6 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mail"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mail"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/templates"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/templates"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/plugin/scheduler"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// declaring this as var to allow overriding in tests
|
// declaring this as var to allow overriding in tests
|
||||||
@@ -1500,8 +1500,8 @@ func (s *Server) initJobs() {
|
|||||||
|
|
||||||
s.Jobs.RegisterJobType(
|
s.Jobs.RegisterJobType(
|
||||||
model.JobTypePlugins,
|
model.JobTypePlugins,
|
||||||
scheduler.MakeWorker(s.Jobs, New(ServerConnector(s.Channels()))),
|
plugins.MakeWorker(s.Jobs, New(ServerConnector(s.Channels()))),
|
||||||
scheduler.MakeScheduler(s.Jobs),
|
plugins.MakeScheduler(s.Jobs),
|
||||||
)
|
)
|
||||||
|
|
||||||
s.Jobs.RegisterJobType(
|
s.Jobs.RegisterJobType(
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import (
|
|||||||
"github.com/stretchr/testify/mock"
|
"github.com/stretchr/testify/mock"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
oauthgitlab "github.com/mattermost/mattermost-server/server/v8/channels/app/oauthproviders/gitlab"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/users"
|
"github.com/mattermost/mattermost-server/server/v8/channels/app/users"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces"
|
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces"
|
||||||
@@ -26,7 +27,6 @@ import (
|
|||||||
storemocks "github.com/mattermost/mattermost-server/server/v8/channels/store/storetest/mocks"
|
storemocks "github.com/mattermost/mattermost-server/server/v8/channels/store/storetest/mocks"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils/testutils"
|
"github.com/mattermost/mattermost-server/server/v8/channels/utils/testutils"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
oauthgitlab "github.com/mattermost/mattermost-server/server/v8/model/oauthproviders/gitlab"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCreateOAuthUser(t *testing.T) {
|
func TestCreateOAuthUser(t *testing.T) {
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (us *UserService) GetProfileImage(user *model.User) ([]byte, bool, error) {
|
|||||||
func (us *UserService) FileBackend() (filestore.FileBackend, error) {
|
func (us *UserService) FileBackend() (filestore.FileBackend, error) {
|
||||||
license := us.license()
|
license := us.license()
|
||||||
insecure := us.config().ServiceSettings.EnableInsecureOutgoingConnections
|
insecure := us.config().ServiceSettings.EnableInsecureOutgoingConnections
|
||||||
backend, err := filestore.NewFileBackend(us.config().FileSettings.ToFileBackendSettings(license != nil && *license.Features.Compliance, insecure != nil && *insecure))
|
backend, err := filestore.NewFileBackend(filestore.NewFileBackendSettingsFromConfig(&us.config().FileSettings, license != nil && *license.Features.Compliance, insecure != nil && *insecure))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
package scheduler
|
package plugins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
package scheduler
|
package plugins
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/jobs"
|
"github.com/mattermost/mattermost-server/server/v8/channels/jobs"
|
||||||
@@ -12,9 +12,9 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils/fileutils"
|
"github.com/mattermost/mattermost-server/server/v8/channels/utils/fileutils"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/model/utils"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/filestore"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/filestore"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,119 +5,9 @@ package utils
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// CopyFile will copy a file from src path to dst path.
|
|
||||||
// Overwrites any existing files at dst.
|
|
||||||
// Permissions are copied from file at src to the new file at dst.
|
|
||||||
func CopyFile(src, dst string) (err error) {
|
|
||||||
in, err := os.Open(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer in.Close()
|
|
||||||
|
|
||||||
if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
out, err := os.Create(dst)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if e := out.Close(); e != nil {
|
|
||||||
err = e
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
_, err = io.Copy(out, in)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
err = out.Sync()
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
stat, err := os.Stat(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
err = os.Chmod(dst, stat.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// CopyDir will copy a directory and all contained files and directories.
|
|
||||||
// src must exist and dst must not exist.
|
|
||||||
// Permissions are preserved when possible. Symlinks are skipped.
|
|
||||||
func CopyDir(src string, dst string) (err error) {
|
|
||||||
src = filepath.Clean(src)
|
|
||||||
dst = filepath.Clean(dst)
|
|
||||||
|
|
||||||
stat, err := os.Stat(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if !stat.IsDir() {
|
|
||||||
return fmt.Errorf("source must be a directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = os.Stat(dst)
|
|
||||||
if err != nil && !os.IsNotExist(err) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
return fmt.Errorf("destination already exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.MkdirAll(dst, stat.Mode())
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
items, err := os.ReadDir(src)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, item := range items {
|
|
||||||
srcPath := filepath.Join(src, item.Name())
|
|
||||||
dstPath := filepath.Join(dst, item.Name())
|
|
||||||
|
|
||||||
if item.IsDir() {
|
|
||||||
err = CopyDir(srcPath, dstPath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
info, ierr := item.Info()
|
|
||||||
if ierr != nil {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.Mode()&os.ModeSymlink != 0 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = CopyFile(srcPath, dstPath)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var SizeLimitExceeded = errors.New("Size limit exceeded")
|
var SizeLimitExceeded = errors.New("Size limit exceeded")
|
||||||
|
|
||||||
type LimitedReaderWithError struct {
|
type LimitedReaderWithError struct {
|
||||||
|
|||||||
@@ -7,63 +7,11 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"crypto/rand"
|
"crypto/rand"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestCopyDir(t *testing.T) {
|
|
||||||
srcDir, err := os.MkdirTemp("", "src")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.RemoveAll(srcDir)
|
|
||||||
|
|
||||||
dstParentDir, err := os.MkdirTemp("", "dstparent")
|
|
||||||
require.NoError(t, err)
|
|
||||||
defer os.RemoveAll(dstParentDir)
|
|
||||||
|
|
||||||
dstDir := filepath.Join(dstParentDir, "dst")
|
|
||||||
|
|
||||||
tempFile := "temp.txt"
|
|
||||||
err = os.WriteFile(filepath.Join(srcDir, tempFile), []byte("test file"), 0655)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
childDir := "child"
|
|
||||||
err = os.Mkdir(filepath.Join(srcDir, childDir), 0777)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
childTempFile := "childtemp.txt"
|
|
||||||
err = os.WriteFile(filepath.Join(srcDir, childDir, childTempFile), []byte("test file"), 0755)
|
|
||||||
require.NoError(t, err)
|
|
||||||
|
|
||||||
err = CopyDir(srcDir, dstDir)
|
|
||||||
assert.NoError(t, err)
|
|
||||||
|
|
||||||
stat, err := os.Stat(filepath.Join(dstDir, tempFile))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint32(0655), uint32(stat.Mode()))
|
|
||||||
assert.False(t, stat.IsDir())
|
|
||||||
data, err := os.ReadFile(filepath.Join(dstDir, tempFile))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "test file", string(data))
|
|
||||||
|
|
||||||
stat, err = os.Stat(filepath.Join(dstDir, childDir))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.True(t, stat.IsDir())
|
|
||||||
|
|
||||||
stat, err = os.Stat(filepath.Join(dstDir, childDir, childTempFile))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, uint32(0755), uint32(stat.Mode()))
|
|
||||||
assert.False(t, stat.IsDir())
|
|
||||||
data, err = os.ReadFile(filepath.Join(dstDir, childDir, childTempFile))
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, "test file", string(data))
|
|
||||||
|
|
||||||
err = CopyDir(srcDir, dstDir)
|
|
||||||
assert.Error(t, err)
|
|
||||||
}
|
|
||||||
func TestLimitedReaderWithError(t *testing.T) {
|
func TestLimitedReaderWithError(t *testing.T) {
|
||||||
t.Run("read less than max size", func(t *testing.T) {
|
t.Run("read less than max size", func(t *testing.T) {
|
||||||
maxBytes := 10
|
maxBytes := 10
|
||||||
|
|||||||
@@ -21,11 +21,11 @@ import (
|
|||||||
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
"github.com/mattermost/mattermost-server/server/v8/channels/app/request"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/store/localcachelayer"
|
"github.com/mattermost/mattermost-server/server/v8/channels/store/localcachelayer"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/store/storetest/mocks"
|
"github.com/mattermost/mattermost-server/server/v8/channels/store/storetest/mocks"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/config"
|
"github.com/mattermost/mattermost-server/server/v8/config"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/plugin"
|
"github.com/mattermost/mattermost-server/server/v8/plugin"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/plugin/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var apiClient *model.Client4
|
var apiClient *model.Client4
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
// Import and register app layer slash commands
|
// Import and register app layer slash commands
|
||||||
_ "github.com/mattermost/mattermost-server/server/v8/channels/app/slashcommands"
|
_ "github.com/mattermost/mattermost-server/server/v8/channels/app/slashcommands"
|
||||||
// Plugins
|
// Plugins
|
||||||
_ "github.com/mattermost/mattermost-server/server/v8/model/oauthproviders/gitlab"
|
_ "github.com/mattermost/mattermost-server/server/v8/channels/app/oauthproviders/gitlab"
|
||||||
|
|
||||||
// Enterprise Imports
|
// Enterprise Imports
|
||||||
_ "github.com/mattermost/mattermost-server/server/v8/channels/imports"
|
_ "github.com/mattermost/mattermost-server/server/v8/channels/imports"
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils/jsonutils"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/model/utils"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/i18n"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/i18n"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -258,7 +258,7 @@ func (s *Store) Load() error {
|
|||||||
loadedCfg := &model.Config{}
|
loadedCfg := &model.Config{}
|
||||||
if len(configBytes) != 0 {
|
if len(configBytes) != 0 {
|
||||||
if err = json.Unmarshal(configBytes, &loadedCfg); err != nil {
|
if err = json.Unmarshal(configBytes, &loadedCfg); err != nil {
|
||||||
return jsonutils.HumanizeJSONError(err, configBytes)
|
return utils.HumanizeJSONError(err, configBytes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5127,6 +5127,10 @@
|
|||||||
"id": "app.file_info.get.app_error",
|
"id": "app.file_info.get.app_error",
|
||||||
"translation": "Unable to get the file info."
|
"translation": "Unable to get the file info."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "app.file_info.get.gif.app_error",
|
||||||
|
"translation": "Could not decode gif."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "app.file_info.get_for_post.app_error",
|
"id": "app.file_info.get_for_post.app_error",
|
||||||
"translation": "Unable to get the file info for the post."
|
"translation": "Unable to get the file info for the post."
|
||||||
@@ -9091,10 +9095,6 @@
|
|||||||
"id": "model.emoji.user_id.app_error",
|
"id": "model.emoji.user_id.app_error",
|
||||||
"translation": "Invalid creator id."
|
"translation": "Invalid creator id."
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"id": "model.file_info.get.gif.app_error",
|
|
||||||
"translation": "Could not decode gif."
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"id": "model.file_info.is_valid.create_at.app_error",
|
"id": "model.file_info.is_valid.create_at.app_error",
|
||||||
"translation": "Invalid value for create_at."
|
"translation": "Invalid value for create_at."
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils/jsonutils"
|
"github.com/mattermost/mattermost-server/server/v8/model/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -56,7 +56,7 @@ func CommandResponseFromJSON(data io.Reader) (*CommandResponse, error) {
|
|||||||
var o CommandResponse
|
var o CommandResponse
|
||||||
err = json.Unmarshal(b, &o)
|
err = json.Unmarshal(b, &o)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, jsonutils.HumanizeJSONError(err, b)
|
return nil, utils.HumanizeJSONError(err, b)
|
||||||
}
|
}
|
||||||
|
|
||||||
o.Attachments = StringifySlackFieldValue(o.Attachments)
|
o.Attachments = StringifySlackFieldValue(o.Attachments)
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ import (
|
|||||||
|
|
||||||
"github.com/mattermost/ldap"
|
"github.com/mattermost/ldap"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/filestore"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -1573,30 +1572,6 @@ func (s *FileSettings) SetDefaults(isUpdate bool) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *FileSettings) ToFileBackendSettings(enableComplianceFeature bool, skipVerify bool) filestore.FileBackendSettings {
|
|
||||||
if *s.DriverName == ImageDriverLocal {
|
|
||||||
return filestore.FileBackendSettings{
|
|
||||||
DriverName: *s.DriverName,
|
|
||||||
Directory: *s.Directory,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return filestore.FileBackendSettings{
|
|
||||||
DriverName: *s.DriverName,
|
|
||||||
AmazonS3AccessKeyId: *s.AmazonS3AccessKeyId,
|
|
||||||
AmazonS3SecretAccessKey: *s.AmazonS3SecretAccessKey,
|
|
||||||
AmazonS3Bucket: *s.AmazonS3Bucket,
|
|
||||||
AmazonS3PathPrefix: *s.AmazonS3PathPrefix,
|
|
||||||
AmazonS3Region: *s.AmazonS3Region,
|
|
||||||
AmazonS3Endpoint: *s.AmazonS3Endpoint,
|
|
||||||
AmazonS3SSL: s.AmazonS3SSL == nil || *s.AmazonS3SSL,
|
|
||||||
AmazonS3SignV2: s.AmazonS3SignV2 != nil && *s.AmazonS3SignV2,
|
|
||||||
AmazonS3SSE: s.AmazonS3SSE != nil && *s.AmazonS3SSE && enableComplianceFeature,
|
|
||||||
AmazonS3Trace: s.AmazonS3Trace != nil && *s.AmazonS3Trace,
|
|
||||||
AmazonS3RequestTimeoutMilliseconds: *s.AmazonS3RequestTimeoutMilliseconds,
|
|
||||||
SkipVerify: skipVerify,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type EmailSettings struct {
|
type EmailSettings struct {
|
||||||
EnableSignUpWithEmail *bool `access:"authentication_email"`
|
EnableSignUpWithEmail *bool `access:"authentication_email"`
|
||||||
EnableSignInWithEmail *bool `access:"authentication_email"`
|
EnableSignInWithEmail *bool `access:"authentication_email"`
|
||||||
|
|||||||
@@ -4,14 +4,10 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"image"
|
|
||||||
"io"
|
|
||||||
"mime"
|
"mime"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils/imgutils"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -150,48 +146,6 @@ func NewInfo(name string) *FileInfo {
|
|||||||
return info
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetInfoForBytes(name string, data io.ReadSeeker, size int) (*FileInfo, *AppError) {
|
|
||||||
info := &FileInfo{
|
|
||||||
Name: name,
|
|
||||||
Size: int64(size),
|
|
||||||
}
|
|
||||||
var err *AppError
|
|
||||||
|
|
||||||
extension := strings.ToLower(filepath.Ext(name))
|
|
||||||
info.MimeType = mime.TypeByExtension(extension)
|
|
||||||
|
|
||||||
if extension != "" {
|
|
||||||
// The client expects a file extension without the leading period
|
|
||||||
info.Extension = extension[1:]
|
|
||||||
} else {
|
|
||||||
info.Extension = extension
|
|
||||||
}
|
|
||||||
|
|
||||||
if info.IsImage() {
|
|
||||||
// Only set the width and height if it's actually an image that we can understand
|
|
||||||
if config, _, err := image.DecodeConfig(data); err == nil {
|
|
||||||
info.Width = config.Width
|
|
||||||
info.Height = config.Height
|
|
||||||
|
|
||||||
if info.MimeType == "image/gif" {
|
|
||||||
// Just show the gif itself instead of a preview image for animated gifs
|
|
||||||
data.Seek(0, io.SeekStart)
|
|
||||||
frameCount, err := imgutils.CountGIFFrames(data)
|
|
||||||
if err != nil {
|
|
||||||
// Still return the rest of the info even though it doesn't appear to be an actual gif
|
|
||||||
info.HasPreviewImage = true
|
|
||||||
return info, NewAppError("GetInfoForBytes", "model.file_info.get.gif.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
|
||||||
}
|
|
||||||
info.HasPreviewImage = frameCount == 1
|
|
||||||
} else {
|
|
||||||
info.HasPreviewImage = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return info, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetEtagForFileInfos(infos []*FileInfo) string {
|
func GetEtagForFileInfos(infos []*FileInfo) string {
|
||||||
if len(infos) == 0 {
|
if len(infos) == 0 {
|
||||||
return Etag()
|
return Etag()
|
||||||
|
|||||||
@@ -4,16 +4,11 @@
|
|||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/base64"
|
|
||||||
_ "image/gif"
|
_ "image/gif"
|
||||||
_ "image/png"
|
_ "image/png"
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFileInfoIsValid(t *testing.T) {
|
func TestFileInfoIsValid(t *testing.T) {
|
||||||
@@ -72,136 +67,3 @@ func TestFileInfoIsImage(t *testing.T) {
|
|||||||
assert.False(t, info.IsImage(), "Text file should not be considered as an image")
|
assert.False(t, info.IsImage(), "Text file should not be considered as an image")
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetInfoForFile(t *testing.T) {
|
|
||||||
fakeFile := make([]byte, 1000)
|
|
||||||
|
|
||||||
pngFile, err := os.ReadFile("../tests/test.png")
|
|
||||||
require.NoError(t, err, "Failed to load test.png")
|
|
||||||
|
|
||||||
// base 64 encoded version of handtinywhite.gif from http://probablyprogramming.com/2009/03/15/the-tiniest-gif-ever
|
|
||||||
gifFile, _ := base64.StdEncoding.DecodeString("R0lGODlhAQABAIABAP///wAAACwAAAAAAQABAAACAkQBADs=")
|
|
||||||
|
|
||||||
animatedGifFile, err := os.ReadFile("../tests/testgif.gif")
|
|
||||||
require.NoError(t, err, "Failed to load testgif.gif")
|
|
||||||
|
|
||||||
var ttc = []struct {
|
|
||||||
testName string
|
|
||||||
filename string
|
|
||||||
file []byte
|
|
||||||
usePrefixForMime bool
|
|
||||||
expectedExtension string
|
|
||||||
expectedSize int
|
|
||||||
expectedMime string
|
|
||||||
expectedWidth int
|
|
||||||
expectedHeight int
|
|
||||||
expectedHasPreviewImage bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
testName: "Text File",
|
|
||||||
filename: "file.txt",
|
|
||||||
file: fakeFile,
|
|
||||||
usePrefixForMime: true,
|
|
||||||
expectedExtension: "txt",
|
|
||||||
expectedSize: 1000,
|
|
||||||
expectedMime: "text/plain",
|
|
||||||
expectedWidth: 0,
|
|
||||||
expectedHeight: 0,
|
|
||||||
expectedHasPreviewImage: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "PNG file",
|
|
||||||
filename: "test.png",
|
|
||||||
file: pngFile,
|
|
||||||
usePrefixForMime: false,
|
|
||||||
expectedExtension: "png",
|
|
||||||
expectedSize: 279591,
|
|
||||||
expectedMime: "image/png",
|
|
||||||
expectedWidth: 408,
|
|
||||||
expectedHeight: 336,
|
|
||||||
expectedHasPreviewImage: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "Static Gif File",
|
|
||||||
filename: "handtinywhite.gif",
|
|
||||||
file: gifFile,
|
|
||||||
usePrefixForMime: false,
|
|
||||||
expectedExtension: "gif",
|
|
||||||
expectedSize: 35,
|
|
||||||
expectedMime: "image/gif",
|
|
||||||
expectedWidth: 1,
|
|
||||||
expectedHeight: 1,
|
|
||||||
expectedHasPreviewImage: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "Animated Gif File",
|
|
||||||
filename: "testgif.gif",
|
|
||||||
file: animatedGifFile,
|
|
||||||
usePrefixForMime: false,
|
|
||||||
expectedExtension: "gif",
|
|
||||||
expectedSize: 38689,
|
|
||||||
expectedMime: "image/gif",
|
|
||||||
expectedWidth: 118,
|
|
||||||
expectedHeight: 118,
|
|
||||||
expectedHasPreviewImage: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
testName: "No extension File",
|
|
||||||
filename: "filewithoutextension",
|
|
||||||
file: fakeFile,
|
|
||||||
usePrefixForMime: false,
|
|
||||||
expectedExtension: "",
|
|
||||||
expectedSize: 1000,
|
|
||||||
expectedMime: "",
|
|
||||||
expectedWidth: 0,
|
|
||||||
expectedHeight: 0,
|
|
||||||
expectedHasPreviewImage: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Always make the extension lower case to make it easier to use in other places
|
|
||||||
testName: "Uppercase extension File",
|
|
||||||
filename: "file.TXT",
|
|
||||||
file: fakeFile,
|
|
||||||
usePrefixForMime: true,
|
|
||||||
expectedExtension: "txt",
|
|
||||||
expectedSize: 1000,
|
|
||||||
expectedMime: "text/plain",
|
|
||||||
expectedWidth: 0,
|
|
||||||
expectedHeight: 0,
|
|
||||||
expectedHasPreviewImage: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Don't error out for image formats we don't support
|
|
||||||
testName: "Not supported File",
|
|
||||||
filename: "file.tif",
|
|
||||||
file: fakeFile,
|
|
||||||
usePrefixForMime: false,
|
|
||||||
expectedExtension: "tif",
|
|
||||||
expectedSize: 1000,
|
|
||||||
expectedMime: "image/tiff",
|
|
||||||
expectedWidth: 0,
|
|
||||||
expectedHeight: 0,
|
|
||||||
expectedHasPreviewImage: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tc := range ttc {
|
|
||||||
t.Run(tc.testName, func(t *testing.T) {
|
|
||||||
info, appErr := GetInfoForBytes(tc.filename, bytes.NewReader(tc.file), len(tc.file))
|
|
||||||
require.Nil(t, appErr)
|
|
||||||
|
|
||||||
assert.Equalf(t, tc.filename, info.Name, "Got incorrect filename: %v", info.Name)
|
|
||||||
assert.Equalf(t, tc.expectedExtension, info.Extension, "Got incorrect extension: %v", info.Extension)
|
|
||||||
assert.EqualValuesf(t, tc.expectedSize, info.Size, "Got incorrect size: %v", info.Size)
|
|
||||||
assert.Equalf(t, tc.expectedWidth, info.Width, "Got incorrect width: %v", info.Width)
|
|
||||||
assert.Equalf(t, tc.expectedHeight, info.Height, "Got incorrect height: %v", info.Height)
|
|
||||||
assert.Equalf(t, tc.expectedHasPreviewImage, info.HasPreviewImage, "Got incorrect has preview image: %v", info.HasPreviewImage)
|
|
||||||
|
|
||||||
if tc.usePrefixForMime {
|
|
||||||
assert.Truef(t, strings.HasPrefix(info.MimeType, tc.expectedMime), "Got incorrect mime type: %v", info.MimeType)
|
|
||||||
} else {
|
|
||||||
assert.Equalf(t, tc.expectedMime, info.MimeType, "Got incorrect mime type: %v", info.MimeType)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
118
server/model/utils/file.go
Обычный файл
118
server/model/utils/file.go
Обычный файл
@@ -0,0 +1,118 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
)
|
||||||
|
|
||||||
|
// CopyFile will copy a file from src path to dst path.
|
||||||
|
// Overwrites any existing files at dst.
|
||||||
|
// Permissions are copied from file at src to the new file at dst.
|
||||||
|
func CopyFile(src, dst string) (err error) {
|
||||||
|
in, err := os.Open(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer in.Close()
|
||||||
|
|
||||||
|
if err = os.MkdirAll(filepath.Dir(dst), os.ModePerm); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
out, err := os.Create(dst)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if e := out.Close(); e != nil {
|
||||||
|
err = e
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
_, err = io.Copy(out, in)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
err = out.Sync()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stat, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
err = os.Chmod(dst, stat.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// CopyDir will copy a directory and all contained files and directories.
|
||||||
|
// src must exist and dst must not exist.
|
||||||
|
// Permissions are preserved when possible. Symlinks are skipped.
|
||||||
|
func CopyDir(src string, dst string) (err error) {
|
||||||
|
src = filepath.Clean(src)
|
||||||
|
dst = filepath.Clean(dst)
|
||||||
|
|
||||||
|
stat, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if !stat.IsDir() {
|
||||||
|
return fmt.Errorf("source must be a directory")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = os.Stat(dst)
|
||||||
|
if err != nil && !os.IsNotExist(err) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
return fmt.Errorf("destination already exists")
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.MkdirAll(dst, stat.Mode())
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
items, err := os.ReadDir(src)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range items {
|
||||||
|
srcPath := filepath.Join(src, item.Name())
|
||||||
|
dstPath := filepath.Join(dst, item.Name())
|
||||||
|
|
||||||
|
if item.IsDir() {
|
||||||
|
err = CopyDir(srcPath, dstPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
info, ierr := item.Info()
|
||||||
|
if ierr != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Mode()&os.ModeSymlink != 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = CopyFile(srcPath, dstPath)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
63
server/model/utils/file_test.go
Обычный файл
63
server/model/utils/file_test.go
Обычный файл
@@ -0,0 +1,63 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCopyDir(t *testing.T) {
|
||||||
|
srcDir, err := os.MkdirTemp("", "src")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(srcDir)
|
||||||
|
|
||||||
|
dstParentDir, err := os.MkdirTemp("", "dstparent")
|
||||||
|
require.NoError(t, err)
|
||||||
|
defer os.RemoveAll(dstParentDir)
|
||||||
|
|
||||||
|
dstDir := filepath.Join(dstParentDir, "dst")
|
||||||
|
|
||||||
|
tempFile := "temp.txt"
|
||||||
|
err = os.WriteFile(filepath.Join(srcDir, tempFile), []byte("test file"), 0655)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
childDir := "child"
|
||||||
|
err = os.Mkdir(filepath.Join(srcDir, childDir), 0777)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
childTempFile := "childtemp.txt"
|
||||||
|
err = os.WriteFile(filepath.Join(srcDir, childDir, childTempFile), []byte("test file"), 0755)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
err = CopyDir(srcDir, dstDir)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
stat, err := os.Stat(filepath.Join(dstDir, tempFile))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, uint32(0655), uint32(stat.Mode()))
|
||||||
|
assert.False(t, stat.IsDir())
|
||||||
|
data, err := os.ReadFile(filepath.Join(dstDir, tempFile))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test file", string(data))
|
||||||
|
|
||||||
|
stat, err = os.Stat(filepath.Join(dstDir, childDir))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.True(t, stat.IsDir())
|
||||||
|
|
||||||
|
stat, err = os.Stat(filepath.Join(dstDir, childDir, childTempFile))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, uint32(0755), uint32(stat.Mode()))
|
||||||
|
assert.False(t, stat.IsDir())
|
||||||
|
data, err = os.ReadFile(filepath.Join(dstDir, childDir, childTempFile))
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "test file", string(data))
|
||||||
|
|
||||||
|
err = CopyDir(srcDir, dstDir)
|
||||||
|
assert.Error(t, err)
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
package jsonutils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
package jsonutils_test
|
package utils_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils/jsonutils"
|
"github.com/mattermost/mattermost-server/server/v8/model/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestHumanizeJsonError(t *testing.T) {
|
func TestHumanizeJsonError(t *testing.T) {
|
||||||
@@ -56,14 +56,14 @@ func TestHumanizeJsonError(t *testing.T) {
|
|||||||
Struct: "struct",
|
Struct: "struct",
|
||||||
Field: "field",
|
Field: "field",
|
||||||
},
|
},
|
||||||
"parsing error at line 3, character 4: json: cannot unmarshal bool into Go struct field struct.field of type jsonutils_test.testType",
|
"parsing error at line 3, character 4: json: cannot unmarshal bool into Go struct field struct.field of type utils_test.testType",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
testCase := testCase
|
testCase := testCase
|
||||||
t.Run(testCase.Description, func(t *testing.T) {
|
t.Run(testCase.Description, func(t *testing.T) {
|
||||||
actual := jsonutils.HumanizeJSONError(testCase.Err, testCase.Data)
|
actual := utils.HumanizeJSONError(testCase.Err, testCase.Data)
|
||||||
if testCase.ExpectedErr == "" {
|
if testCase.ExpectedErr == "" {
|
||||||
assert.NoError(t, actual)
|
assert.NoError(t, actual)
|
||||||
} else {
|
} else {
|
||||||
@@ -81,7 +81,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
Data []byte
|
Data []byte
|
||||||
Offset int64
|
Offset int64
|
||||||
Err error
|
Err error
|
||||||
Expected *jsonutils.HumanizedJSONError
|
Expected *utils.HumanizedJSONError
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
"nil error",
|
"nil error",
|
||||||
@@ -95,7 +95,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
-1,
|
-1,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "invalid offset -1"),
|
Err: errors.Wrap(errors.New("message"), "invalid offset -1"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -104,7 +104,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
0,
|
0,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 1"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 1"),
|
||||||
Line: 1,
|
Line: 1,
|
||||||
Character: 1,
|
Character: 1,
|
||||||
@@ -115,7 +115,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
5,
|
5,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 6"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 6"),
|
||||||
Line: 1,
|
Line: 1,
|
||||||
Character: 6,
|
Character: 6,
|
||||||
@@ -126,7 +126,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
6,
|
6,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 7"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 1, character 7"),
|
||||||
Line: 1,
|
Line: 1,
|
||||||
Character: 7,
|
Character: 7,
|
||||||
@@ -137,7 +137,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
7,
|
7,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 1"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 1"),
|
||||||
Line: 2,
|
Line: 2,
|
||||||
Character: 1,
|
Character: 1,
|
||||||
@@ -148,7 +148,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
12,
|
12,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 6"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 6"),
|
||||||
Line: 2,
|
Line: 2,
|
||||||
Character: 6,
|
Character: 6,
|
||||||
@@ -159,7 +159,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
13,
|
13,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 7"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 2, character 7"),
|
||||||
Line: 2,
|
Line: 2,
|
||||||
Character: 7,
|
Character: 7,
|
||||||
@@ -170,7 +170,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
17,
|
17,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 4"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 4"),
|
||||||
Line: 3,
|
Line: 3,
|
||||||
Character: 4,
|
Character: 4,
|
||||||
@@ -181,7 +181,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
19,
|
19,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 6"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 6"),
|
||||||
Line: 3,
|
Line: 3,
|
||||||
Character: 6,
|
Character: 6,
|
||||||
@@ -192,7 +192,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
20,
|
20,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 7"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 3, character 7"),
|
||||||
Line: 3,
|
Line: 3,
|
||||||
Character: 7,
|
Character: 7,
|
||||||
@@ -203,7 +203,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3\n"),
|
[]byte("line 1\nline 2\nline 3\n"),
|
||||||
21,
|
21,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "parsing error at line 4, character 1"),
|
Err: errors.Wrap(errors.New("message"), "parsing error at line 4, character 1"),
|
||||||
Line: 4,
|
Line: 4,
|
||||||
Character: 1,
|
Character: 1,
|
||||||
@@ -214,7 +214,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
[]byte("line 1\nline 2\nline 3"),
|
[]byte("line 1\nline 2\nline 3"),
|
||||||
21,
|
21,
|
||||||
errors.New("message"),
|
errors.New("message"),
|
||||||
&jsonutils.HumanizedJSONError{
|
&utils.HumanizedJSONError{
|
||||||
Err: errors.Wrap(errors.New("message"), "invalid offset 21"),
|
Err: errors.Wrap(errors.New("message"), "invalid offset 21"),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -223,7 +223,7 @@ func TestNewHumanizedJSONError(t *testing.T) {
|
|||||||
for _, testCase := range testCases {
|
for _, testCase := range testCases {
|
||||||
testCase := testCase
|
testCase := testCase
|
||||||
t.Run(testCase.Description, func(t *testing.T) {
|
t.Run(testCase.Description, func(t *testing.T) {
|
||||||
actual := jsonutils.NewHumanizedJSONError(testCase.Err, testCase.Data, testCase.Offset)
|
actual := utils.NewHumanizedJSONError(testCase.Err, testCase.Data, testCase.Offset)
|
||||||
if testCase.Expected != nil && actual.Err != nil {
|
if testCase.Expected != nil && actual.Err != nil {
|
||||||
if assert.EqualValues(t, testCase.Expected.Err.Error(), actual.Err.Error()) {
|
if assert.EqualValues(t, testCase.Expected.Err.Error(), actual.Err.Error()) {
|
||||||
actual.Err = testCase.Expected.Err
|
actual.Err = testCase.Expected.Err
|
||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -57,6 +58,30 @@ type FileBackendSettings struct {
|
|||||||
AmazonS3RequestTimeoutMilliseconds int64
|
AmazonS3RequestTimeoutMilliseconds int64
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFileBackendSettingsFromConfig(fileSettings *model.FileSettings, enableComplianceFeature bool, skipVerify bool) FileBackendSettings {
|
||||||
|
if *fileSettings.DriverName == model.ImageDriverLocal {
|
||||||
|
return FileBackendSettings{
|
||||||
|
DriverName: *fileSettings.DriverName,
|
||||||
|
Directory: *fileSettings.Directory,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return FileBackendSettings{
|
||||||
|
DriverName: *fileSettings.DriverName,
|
||||||
|
AmazonS3AccessKeyId: *fileSettings.AmazonS3AccessKeyId,
|
||||||
|
AmazonS3SecretAccessKey: *fileSettings.AmazonS3SecretAccessKey,
|
||||||
|
AmazonS3Bucket: *fileSettings.AmazonS3Bucket,
|
||||||
|
AmazonS3PathPrefix: *fileSettings.AmazonS3PathPrefix,
|
||||||
|
AmazonS3Region: *fileSettings.AmazonS3Region,
|
||||||
|
AmazonS3Endpoint: *fileSettings.AmazonS3Endpoint,
|
||||||
|
AmazonS3SSL: fileSettings.AmazonS3SSL == nil || *fileSettings.AmazonS3SSL,
|
||||||
|
AmazonS3SignV2: fileSettings.AmazonS3SignV2 != nil && *fileSettings.AmazonS3SignV2,
|
||||||
|
AmazonS3SSE: fileSettings.AmazonS3SSE != nil && *fileSettings.AmazonS3SSE && enableComplianceFeature,
|
||||||
|
AmazonS3Trace: fileSettings.AmazonS3Trace != nil && *fileSettings.AmazonS3Trace,
|
||||||
|
AmazonS3RequestTimeoutMilliseconds: *fileSettings.AmazonS3RequestTimeoutMilliseconds,
|
||||||
|
SkipVerify: skipVerify,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (settings *FileBackendSettings) CheckMandatoryS3Fields() error {
|
func (settings *FileBackendSettings) CheckMandatoryS3Fields() error {
|
||||||
if settings.AmazonS3Bucket == "" {
|
if settings.AmazonS3Bucket == "" {
|
||||||
return errors.New("missing s3 bucket settings")
|
return errors.New("missing s3 bucket settings")
|
||||||
|
|||||||
@@ -18,17 +18,11 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
s3 "github.com/minio/minio-go/v7"
|
s3 "github.com/minio/minio-go/v7"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Copied from model/config.go to avoid an import cycle
|
|
||||||
const (
|
|
||||||
MinioAccessKey = "minioaccesskey"
|
|
||||||
MinioSecretKey = "miniosecretkey"
|
|
||||||
ImageDriverS3 = "amazons3"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestCheckMandatoryS3Fields(t *testing.T) {
|
func TestCheckMandatoryS3Fields(t *testing.T) {
|
||||||
cfg := FileBackendSettings{}
|
cfg := FileBackendSettings{}
|
||||||
|
|
||||||
@@ -69,9 +63,9 @@ func TestMakeBucket(t *testing.T) {
|
|||||||
bucketName = strings.Replace(bucketName, "/", "", -1)
|
bucketName = strings.Replace(bucketName, "/", "", -1)
|
||||||
|
|
||||||
cfg := FileBackendSettings{
|
cfg := FileBackendSettings{
|
||||||
DriverName: ImageDriverS3,
|
DriverName: model.ImageDriverS3,
|
||||||
AmazonS3AccessKeyId: MinioAccessKey,
|
AmazonS3AccessKeyId: model.MinioAccessKey,
|
||||||
AmazonS3SecretAccessKey: MinioSecretKey,
|
AmazonS3SecretAccessKey: model.MinioSecretKey,
|
||||||
AmazonS3Bucket: bucketName,
|
AmazonS3Bucket: bucketName,
|
||||||
AmazonS3Endpoint: s3Endpoint,
|
AmazonS3Endpoint: s3Endpoint,
|
||||||
AmazonS3Region: "",
|
AmazonS3Region: "",
|
||||||
@@ -110,9 +104,9 @@ func TestTimeout(t *testing.T) {
|
|||||||
bucketName = strings.Replace(bucketName, "/", "", -1)
|
bucketName = strings.Replace(bucketName, "/", "", -1)
|
||||||
|
|
||||||
cfg := FileBackendSettings{
|
cfg := FileBackendSettings{
|
||||||
DriverName: ImageDriverS3,
|
DriverName: model.ImageDriverS3,
|
||||||
AmazonS3AccessKeyId: MinioAccessKey,
|
AmazonS3AccessKeyId: model.MinioAccessKey,
|
||||||
AmazonS3SecretAccessKey: MinioSecretKey,
|
AmazonS3SecretAccessKey: model.MinioSecretKey,
|
||||||
AmazonS3Bucket: bucketName,
|
AmazonS3Bucket: bucketName,
|
||||||
AmazonS3Endpoint: s3Endpoint,
|
AmazonS3Endpoint: s3Endpoint,
|
||||||
AmazonS3Region: "",
|
AmazonS3Region: "",
|
||||||
@@ -171,9 +165,9 @@ func TestInsecureMakeBucket(t *testing.T) {
|
|||||||
bucketName = strings.Replace(bucketName, "/", "", -1)
|
bucketName = strings.Replace(bucketName, "/", "", -1)
|
||||||
|
|
||||||
cfg := FileBackendSettings{
|
cfg := FileBackendSettings{
|
||||||
DriverName: ImageDriverS3,
|
DriverName: model.ImageDriverS3,
|
||||||
AmazonS3AccessKeyId: MinioAccessKey,
|
AmazonS3AccessKeyId: model.MinioAccessKey,
|
||||||
AmazonS3SecretAccessKey: MinioSecretKey,
|
AmazonS3SecretAccessKey: model.MinioSecretKey,
|
||||||
AmazonS3Bucket: bucketName,
|
AmazonS3Bucket: bucketName,
|
||||||
AmazonS3Endpoint: proxySelfSignedHTTPS.URL[8:],
|
AmazonS3Endpoint: proxySelfSignedHTTPS.URL[8:],
|
||||||
AmazonS3Region: "",
|
AmazonS3Region: "",
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
//nolint:misspell
|
||||||
package markdown
|
package markdown
|
||||||
|
|
||||||
var htmlEntities = map[string]string{
|
var htmlEntities = map[string]string{
|
||||||
|
|||||||
@@ -11,14 +11,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
timePkg "time"
|
timePkg "time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiTimerLayer struct {
|
type apiTimerLayer struct {
|
||||||
pluginID string
|
pluginID string
|
||||||
apiImpl API
|
apiImpl API
|
||||||
metrics einterfaces.MetricsInterface
|
metrics metricsInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *apiTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
|
func (api *apiTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
|
||||||
|
|||||||
@@ -13,9 +13,8 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/model/utils"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -52,7 +51,7 @@ type Environment struct {
|
|||||||
registeredPlugins sync.Map
|
registeredPlugins sync.Map
|
||||||
pluginHealthCheckJob *PluginHealthCheckJob
|
pluginHealthCheckJob *PluginHealthCheckJob
|
||||||
logger *mlog.Logger
|
logger *mlog.Logger
|
||||||
metrics einterfaces.MetricsInterface
|
metrics metricsInterface
|
||||||
newAPIImpl apiImplCreatorFunc
|
newAPIImpl apiImplCreatorFunc
|
||||||
dbDriver Driver
|
dbDriver Driver
|
||||||
pluginDir string
|
pluginDir string
|
||||||
@@ -67,7 +66,7 @@ func NewEnvironment(
|
|||||||
pluginDir string,
|
pluginDir string,
|
||||||
webappPluginDir string,
|
webappPluginDir string,
|
||||||
logger *mlog.Logger,
|
logger *mlog.Logger,
|
||||||
metrics einterfaces.MetricsInterface,
|
metrics metricsInterface,
|
||||||
) (*Environment, error) {
|
) (*Environment, error) {
|
||||||
return &Environment{
|
return &Environment{
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/plugin/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPluginHealthCheck(t *testing.T) {
|
func TestPluginHealthCheck(t *testing.T) {
|
||||||
|
|||||||
@@ -11,14 +11,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
timePkg "time"
|
timePkg "time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type hooksTimerLayer struct {
|
type hooksTimerLayer struct {
|
||||||
pluginID string
|
pluginID string
|
||||||
hooksImpl Hooks
|
hooksImpl Hooks
|
||||||
metrics einterfaces.MetricsInterface
|
metrics metricsInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hooks *hooksTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
|
func (hooks *hooksTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
|
||||||
|
|||||||
@@ -453,14 +453,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
timePkg "time"
|
timePkg "time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiTimerLayer struct {
|
type apiTimerLayer struct {
|
||||||
pluginID string
|
pluginID string
|
||||||
apiImpl API
|
apiImpl API
|
||||||
metrics einterfaces.MetricsInterface
|
metrics metricsInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *apiTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
|
func (api *apiTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
|
||||||
@@ -495,14 +494,13 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
timePkg "time"
|
timePkg "time"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
type hooksTimerLayer struct {
|
type hooksTimerLayer struct {
|
||||||
pluginID string
|
pluginID string
|
||||||
hooksImpl Hooks
|
hooksImpl Hooks
|
||||||
metrics einterfaces.MetricsInterface
|
metrics metricsInterface
|
||||||
}
|
}
|
||||||
|
|
||||||
func (hooks *hooksTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
|
func (hooks *hooksTimerLayer) recordTime(startTime timePkg.Time, name string, success bool) {
|
||||||
|
|||||||
11
server/plugin/metrics.go
Обычный файл
11
server/plugin/metrics.go
Обычный файл
@@ -0,0 +1,11 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
package plugin
|
||||||
|
|
||||||
|
type metricsInterface interface {
|
||||||
|
ObservePluginHookDuration(pluginID, hookName string, success bool, elapsed float64)
|
||||||
|
ObservePluginMultiHookIterationDuration(pluginID string, elapsed float64)
|
||||||
|
ObservePluginMultiHookDuration(elapsed float64)
|
||||||
|
ObservePluginAPIDuration(pluginID, apiName string, success bool, elapsed float64)
|
||||||
|
}
|
||||||
@@ -18,7 +18,6 @@ import (
|
|||||||
plugin "github.com/hashicorp/go-plugin"
|
plugin "github.com/hashicorp/go-plugin"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/einterfaces"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
||||||
)
|
)
|
||||||
@@ -32,7 +31,7 @@ type supervisor struct {
|
|||||||
hooksClient *hooksRPCClient
|
hooksClient *hooksRPCClient
|
||||||
}
|
}
|
||||||
|
|
||||||
func newSupervisor(pluginInfo *model.BundleInfo, apiImpl API, driver Driver, parentLogger *mlog.Logger, metrics einterfaces.MetricsInterface) (retSupervisor *supervisor, retErr error) {
|
func newSupervisor(pluginInfo *model.BundleInfo, apiImpl API, driver Driver, parentLogger *mlog.Logger, metrics metricsInterface) (retSupervisor *supervisor, retErr error) {
|
||||||
sup := supervisor{}
|
sup := supervisor{}
|
||||||
defer func() {
|
defer func() {
|
||||||
if retErr != nil {
|
if retErr != nil {
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ import (
|
|||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/channels/utils"
|
|
||||||
"github.com/mattermost/mattermost-server/server/v8/model"
|
"github.com/mattermost/mattermost-server/server/v8/model"
|
||||||
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
"github.com/mattermost/mattermost-server/server/v8/platform/shared/mlog"
|
||||||
|
"github.com/mattermost/mattermost-server/server/v8/plugin/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestSupervisor(t *testing.T) {
|
func TestSupervisor(t *testing.T) {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user