mirror of
https://github.com/mattermost/mattermost.git
synced 2026-04-12 04:27:39 -04:00
fix: only match root-level JSONL files when importing a zip
When importing a Mattermost export zip, the code iterated over all files to find the first .jsonl by extension. Exported attachments under data/ could themselves be .jsonl files, causing the import to pick an attachment as the manifest instead of the actual root-level JSONL file. Extract an IsRootJsonlFile helper in the imports package and use it in the import process worker, mmctl validator, and bulk import test to restrict the search to files with no directory component.
This commit is contained in:
parent
885ebdd4f1
commit
ad7f230f06
5 changed files with 41 additions and 23 deletions
|
|
@ -650,7 +650,7 @@ func TestImportBulkImportWithAttachments(t *testing.T) {
|
|||
|
||||
var jsonFile io.ReadCloser
|
||||
for _, f := range importZipReader.File {
|
||||
if filepath.Ext(f.Name) != ".jsonl" {
|
||||
if !imports.IsRootJsonlFile(f.Name) {
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,6 +16,14 @@ import (
|
|||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
)
|
||||
|
||||
// IsRootJsonlFile reports whether the given zip entry name refers to a .jsonl
|
||||
// file at the root of the archive (no directory component). This is used to
|
||||
// locate the import manifest while ignoring .jsonl files that may exist as
|
||||
// exported attachments in subdirectories.
|
||||
func IsRootJsonlFile(name string) bool {
|
||||
return filepath.Ext(name) == ".jsonl" && filepath.Dir(name) == "."
|
||||
}
|
||||
|
||||
func ValidateSchemeImportData(data *SchemeImportData) *model.AppError {
|
||||
if data.Scope == nil {
|
||||
return model.NewAppError("BulkImport", "app.import.validate_scheme_import_data.null_scope.error", nil, "", http.StatusBadRequest)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,18 @@ import (
|
|||
"github.com/mattermost/mattermost/server/v8/channels/utils/fileutils"
|
||||
)
|
||||
|
||||
func TestIsRootJsonlFile(t *testing.T) {
|
||||
assert.True(t, IsRootJsonlFile("import.jsonl"))
|
||||
assert.True(t, IsRootJsonlFile("export.jsonl"))
|
||||
assert.False(t, IsRootJsonlFile("data/export.jsonl"))
|
||||
assert.False(t, IsRootJsonlFile("data/attachments/report.jsonl"))
|
||||
assert.False(t, IsRootJsonlFile("data/deep/nested/file.jsonl"))
|
||||
assert.False(t, IsRootJsonlFile("import.json"))
|
||||
assert.False(t, IsRootJsonlFile("import.zip"))
|
||||
assert.False(t, IsRootJsonlFile("data/photo.jpg"))
|
||||
assert.False(t, IsRootJsonlFile(""))
|
||||
}
|
||||
|
||||
func TestImportValidateSchemeImportData(t *testing.T) {
|
||||
// Test with minimum required valid properties and team scope.
|
||||
data := SchemeImportData{
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"github.com/mattermost/mattermost/server/public/shared/configservice"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/public/shared/request"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/app/imports"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/jobs"
|
||||
"github.com/mattermost/mattermost/server/v8/platform/shared/filestore"
|
||||
)
|
||||
|
|
@ -100,29 +101,28 @@ func MakeWorker(jobServer *jobs.JobServer, app AppIface) *jobs.SimpleWorker {
|
|||
}
|
||||
|
||||
// find JSONL import file.
|
||||
var jsonFile io.ReadCloser
|
||||
var jsonZipFile *zip.File
|
||||
for _, f := range importZipReader.File {
|
||||
if filepath.Ext(f.Name) != ".jsonl" {
|
||||
continue
|
||||
if imports.IsRootJsonlFile(f.Name) {
|
||||
jsonZipFile = f
|
||||
break
|
||||
}
|
||||
// avoid "zip slip"
|
||||
if strings.Contains(f.Name, "..") {
|
||||
return model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, "jsonFilePath contains path traversal", http.StatusForbidden)
|
||||
}
|
||||
|
||||
jsonFile, err = f.Open()
|
||||
if err != nil {
|
||||
return model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
|
||||
defer jsonFile.Close()
|
||||
break
|
||||
}
|
||||
|
||||
if jsonFile == nil {
|
||||
if jsonZipFile == nil {
|
||||
return model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.missing_jsonl", nil, "jsonFile was nil", http.StatusBadRequest)
|
||||
}
|
||||
|
||||
// avoid "zip slip"
|
||||
if strings.Contains(jsonZipFile.Name, "..") {
|
||||
return model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, "jsonFilePath contains path traversal", http.StatusForbidden)
|
||||
}
|
||||
|
||||
jsonFile, err := jsonZipFile.Open()
|
||||
if err != nil {
|
||||
return model.NewAppError("ImportProcessWorker", "import_process.worker.do_job.open_file", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
}
|
||||
defer jsonFile.Close()
|
||||
|
||||
extractContent := job.Data["extract_content"] == "true"
|
||||
// do the actual import.
|
||||
lineNumber, appErr := app.BulkImportWithPath(appContext, jsonFile, importZipReader, false, extractContent, runtime.NumCPU(), model.ExportDataDir)
|
||||
|
|
|
|||
|
|
@ -248,12 +248,10 @@ func (v *Validator) Validate() error {
|
|||
|
||||
var jsonlZip *zip.File
|
||||
for _, zfile := range z.File {
|
||||
if filepath.Ext(zfile.Name) != ".jsonl" {
|
||||
continue
|
||||
if imports.IsRootJsonlFile(zfile.Name) {
|
||||
jsonlZip = zfile
|
||||
break
|
||||
}
|
||||
|
||||
jsonlZip = zfile
|
||||
break
|
||||
}
|
||||
if jsonlZip == nil {
|
||||
return fmt.Errorf("could not find a .jsonl file in the import archive")
|
||||
|
|
|
|||
Loading…
Reference in a new issue