mirror of
https://github.com/siderolabs/image-factory.git
synced 2025-09-21 13:51:08 +02:00
S3 does not set proper Content-Disposition, and this header might be ignored by e.g. browsers if set before redirect. We need to include it when adding new object to the cache. Additionally, if the object was previously cached, we need to ensure that Content-Disposition header will be properly updated. Signed-off-by: Mateusz Urbanek <mateusz.urbanek@siderolabs.com>
102 lines
2.4 KiB
Go
102 lines
2.4 KiB
Go
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
|
|
|
package http
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"mime"
|
|
"net/http"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/blang/semver/v4"
|
|
"github.com/julienschmidt/httprouter"
|
|
"go.uber.org/zap"
|
|
|
|
"github.com/siderolabs/image-factory/internal/asset/cache"
|
|
"github.com/siderolabs/image-factory/internal/profile"
|
|
)
|
|
|
|
// handleImage handles downloading of boot assets.
|
|
func (f *Frontend) handleImage(ctx context.Context, w http.ResponseWriter, r *http.Request, p httprouter.Params) error {
|
|
schematicID := p.ByName("schematic")
|
|
|
|
schematic, err := f.schematicFactory.Get(ctx, schematicID)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
versionTag := p.ByName("version")
|
|
if !strings.HasPrefix(versionTag, "v") {
|
|
versionTag = "v" + versionTag
|
|
}
|
|
|
|
version, err := semver.Parse(versionTag[1:])
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing version: %w", err)
|
|
}
|
|
|
|
path := p.ByName("path")
|
|
|
|
prof, err := profile.ParseFromPath(path, version.String())
|
|
if err != nil {
|
|
return fmt.Errorf("error parsing profile from path: %w", err)
|
|
}
|
|
|
|
prof, err = profile.EnhanceFromSchematic(ctx, prof, schematic, f.artifactsManager, f.secureBootService, versionTag)
|
|
if err != nil {
|
|
return fmt.Errorf("error enhancing profile from schematic: %w", err)
|
|
}
|
|
|
|
if err = prof.Validate(); err != nil {
|
|
return fmt.Errorf("error validating profile: %w", err)
|
|
}
|
|
|
|
asset, err := f.assetBuilder.Build(ctx, prof, version.String(), path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if asset, ok := asset.(cache.RedirectableAsset); ok && r.Method != http.MethodHead {
|
|
var url string
|
|
|
|
url, err = asset.Redirect()
|
|
if err == nil {
|
|
http.Redirect(w, r, url, http.StatusFound)
|
|
|
|
return nil
|
|
}
|
|
|
|
f.logger.Warn("asset does not support redirection, serving directly", zap.Error(err))
|
|
}
|
|
|
|
w.Header().Set("Content-Length", strconv.FormatInt(asset.Size(), 10))
|
|
|
|
if ext := filepath.Ext(path); ext != "" {
|
|
w.Header().Set("Content-Type", mime.TypeByExtension(ext))
|
|
}
|
|
|
|
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, path))
|
|
w.WriteHeader(http.StatusOK)
|
|
|
|
if r.Method == http.MethodHead {
|
|
return nil
|
|
}
|
|
|
|
reader, err := asset.Reader()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer reader.Close() //nolint:errcheck
|
|
|
|
_, err = io.Copy(w, reader)
|
|
|
|
return err
|
|
}
|