From bcc6654300fdf6e6501b99d9d0a12884a8cce36b Mon Sep 17 00:00:00 2001 From: Code by Ben Date: Thu, 12 Sep 2024 11:41:44 +1000 Subject: [PATCH] repair artifacts commands use a 1min timeout and has a test case. Artifact handler now does not panic with 404 errors. --- handler/app/dirs.go | 22 +++++++++++++-- internal/config/config_test.go | 39 +++++++++++++++++++++++++++ internal/config/repair.go | 27 +++++++++++-------- internal/config/testdata/IMPLODE.ZIP | Bin 0 -> 26375 bytes 4 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 internal/config/testdata/IMPLODE.ZIP diff --git a/handler/app/dirs.go b/handler/app/dirs.go index 231e0132..35f0ba01 100644 --- a/handler/app/dirs.go +++ b/handler/app/dirs.go @@ -73,7 +73,7 @@ type Dirs struct { func (dir Dirs) Artifact(c echo.Context, db *sql.DB, logger *zap.SugaredLogger, readonly bool) error { const name = "artifact" art, err := dir.modelsFile(c, db) - if err != nil { + if art404 := art == nil || err != nil; art404 { return err } data := empty(c) @@ -113,6 +113,9 @@ func (dir Dirs) Artifact(c echo.Context, db *sql.DB, logger *zap.SugaredLogger, } func (dir Dirs) embed(art *models.File, data map[string]interface{}) (map[string]interface{}, error) { + if art == nil { + return data, nil + } p, err := readme.Read(art, dir.Download, dir.Extra) if err != nil { if errors.Is(err, render.ErrDownload) { @@ -247,6 +250,9 @@ func (dir Dirs) assets(nameDir, unid string) map[string][2]string { // missingAssets returns a string of missing assets for the file record of the artifact. func (dir Dirs) missingAssets(art *models.File) string { + if art == nil { + return "" + } uid := art.UUID.String missing := []string{} dl := helper.File(filepath.Join(dir.Download, uid)) @@ -276,6 +282,9 @@ func (dir Dirs) missingAssets(art *models.File) string { // attributions returns the author attributions for the file record of the artifact. func (dir Dirs) attributions(art *models.File, data map[string]interface{}) map[string]interface{} { + if art == nil { + return data + } data["writers"] = filerecord.AttrWriter(art) data["artists"] = filerecord.AttrArtist(art) data["programmers"] = filerecord.AttrProg(art) @@ -285,6 +294,9 @@ func (dir Dirs) attributions(art *models.File, data map[string]interface{}) map[ // filemetadata returns the file metadata for the file record of the artifact. func (dir Dirs) filemetadata(art *models.File, data map[string]interface{}) map[string]interface{} { + if art == nil { + return data + } data["filename"] = filerecord.Basename(art) data["filesize"] = simple.BytesHuman(art.Filesize.Int64) data["filebyte"] = art.Filesize @@ -303,6 +315,9 @@ func (dir Dirs) filemetadata(art *models.File, data map[string]interface{}) map[ // otherRelations returns the other relations and external links for the file record of the artifact. func (dir Dirs) otherRelations(art *models.File, data map[string]interface{}) map[string]interface{} { + if art == nil { + return data + } data["relations"] = filerecord.Relations(art) data["websites"] = filerecord.Websites(art) data["demozoo"] = filerecord.IdenficationDZ(art) @@ -405,7 +420,7 @@ func errorWithID(err error, key string, id any) error { // embedText embeds the readme or file download text content for the file record of the artifact. func embedText(art *models.File, data map[string]interface{}, b ...byte) (map[string]interface{}, error) { - if len(b) == 0 { + if len(b) == 0 || art == nil { return data, nil } const ( @@ -482,6 +497,9 @@ func decode(src io.Reader) (string, error) { // firstLead returns the lead for the file record which is the filename and releasers. func firstLead(art *models.File) string { + if art == nil { + return "" + } fname := art.Filename.String span := fmt.Sprintf("%s ", fname) return fmt.Sprintf("%s
%s", releasersHrefs(art), span) diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 361f9f0d..ae22aaba 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -4,9 +4,13 @@ import ( "bytes" "context" "os" + "path/filepath" "testing" + "github.com/Defacto2/helper" + "github.com/Defacto2/magicnumber" "github.com/Defacto2/server/internal/config" + "github.com/Defacto2/server/internal/zaplog" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -123,3 +127,38 @@ func TestRepair(t *testing.T) { err = config.RemoveImage("", "", "") require.Error(t, err) } + +func TestReArchive(t *testing.T) { + t.Parallel() + r := config.Zip + logger := zaplog.Timestamp().Sugar() + ctx := context.WithValue(context.Background(), helper.LoggerKey, logger) + err := r.ReArchive(ctx, "", "", "") + require.Error(t, err) + + // test the archive that uses the defunct implode method + src, err := filepath.Abs(filepath.Join("testdata", "IMPLODE.ZIP")) + require.NoError(t, err) + readr, err := os.Open(src) + require.NoError(t, err) + defer readr.Close() + sign := magicnumber.Find(readr) + assert.Equal(t, magicnumber.PKWAREZipImplode, sign) + + err = r.ReArchive(ctx, src, "", "") + require.Error(t, err) + dst := filepath.Dir(src) + err = r.ReArchive(ctx, src, dst, "") + require.Error(t, err) + err = r.ReArchive(ctx, src, dst, "newfile") + require.NoError(t, err) + + // test the new, re-created archive that uses the common deflate method + name := filepath.Join(dst, "newfile.zip") + readr, err = os.Open(name) + require.NoError(t, err) + defer readr.Close() + sign = magicnumber.Find(readr) + assert.Equal(t, magicnumber.PKWAREZip, sign) + defer os.Remove(name) +} diff --git a/internal/config/repair.go b/internal/config/repair.go index 77e92077..3bba1492 100644 --- a/internal/config/repair.go +++ b/internal/config/repair.go @@ -64,7 +64,7 @@ func (c Config) Archives(ctx context.Context, exec boil.ContextExecutor) error { if uid == "" || fixzip.Invalid(ctx, path) { return nil } - if err := Zip.rearchive(ctx, path, c.AbsExtra, uid); err != nil { + if err := Zip.ReArchive(ctx, path, c.AbsExtra, uid); err != nil { return fmt.Errorf("zip repair and re-archive: %w", err) } return nil @@ -77,7 +77,7 @@ func (c Config) Archives(ctx context.Context, exec boil.ContextExecutor) error { if uid == "" || fixlha.Invalid(ctx, path) { return nil } - if err := LHA.rearchive(ctx, path, c.AbsExtra, uid); err != nil { + if err := LHA.ReArchive(ctx, path, c.AbsExtra, uid); err != nil { return fmt.Errorf("lha/lzh repair and re-archive: %w", err) } return nil @@ -90,7 +90,7 @@ func (c Config) Archives(ctx context.Context, exec boil.ContextExecutor) error { if uid == "" || fixarc.Invalid(ctx, path) { return nil } - if err := Arc.rearchive(ctx, path, c.AbsExtra, uid); err != nil { + if err := Arc.ReArchive(ctx, path, c.AbsExtra, uid); err != nil { return fmt.Errorf("arc repair and re-archive: %w", err) } return nil @@ -103,7 +103,7 @@ func (c Config) Archives(ctx context.Context, exec boil.ContextExecutor) error { if uid == "" || fixarj.Invalid(ctx, path) { return nil } - if err := Arj.rearchive(ctx, path, c.AbsExtra, uid); err != nil { + if err := Arj.ReArchive(ctx, path, c.AbsExtra, uid); err != nil { return fmt.Errorf("arj repair and re-archive: %w", err) } return nil @@ -209,11 +209,14 @@ func (r Repair) artifacts(ctx context.Context, exec boil.ContextExecutor, logger return artifacts, nil } -func (r Repair) rearchive(ctx context.Context, path, extra, uid string) error { +func (r Repair) ReArchive(ctx context.Context, src, destDir, uid string) error { + if src == "" || destDir == "" || uid == "" { + return fmt.Errorf("rearchive %s %w: %q %q %q", r, ErrEmpty, src, destDir, uid) + } logger := helper.Logger(ctx) tmp, err := os.MkdirTemp(helper.TmpDir(), "rearchive-") if err != nil { - return fmt.Errorf("rearchive mkdir temp %w: %s", err, path) + return fmt.Errorf("rearchive mkdir temp %w: %s", err, src) } defer os.RemoveAll(tmp) @@ -228,12 +231,14 @@ func (r Repair) rearchive(ctx context.Context, path, extra, uid string) error { case Arj: extractCmd, extractArg = command.Zip7, "x" } - cmd := exec.Command(extractCmd, extractArg, path) + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Minute) + defer cancel() + cmd := exec.CommandContext(ctx, extractCmd, extractArg, src) cmd.Dir = tmp - if err = cmd.Run(); err != nil { - return fmt.Errorf("rearchive run %w: %s", err, path) + if stdoutStderr, err := cmd.CombinedOutput(); err != nil { + return fmt.Errorf("rearchive run %w: %s: dump: %q", + err, src, stdoutStderr) } - c, err := helper.Count(tmp) if err != nil { return fmt.Errorf("rearchive tmp count %w: %s", err, tmp) @@ -252,7 +257,7 @@ func (r Repair) rearchive(ctx context.Context, path, extra, uid string) error { return nil } - finalArc := filepath.Join(extra, basename) + finalArc := filepath.Join(destDir, basename) if err = helper.RenameCrossDevice(tmpArc, finalArc); err != nil { defer os.RemoveAll(tmpArc) return fmt.Errorf("rearchive rename %w: %s", err, tmpArc) diff --git a/internal/config/testdata/IMPLODE.ZIP b/internal/config/testdata/IMPLODE.ZIP new file mode 100644 index 0000000000000000000000000000000000000000..149ab38b91a2abd292e2ae500945f74f54c531ab GIT binary patch literal 26375 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 T=RAF3KF|kl=N@|cxB>qI6lAYV literal 0 HcmV?d00001