| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112 |
- package services
- import (
- "archive/tar"
- "compress/gzip"
- "context"
- "fmt"
- "io"
- "path"
- "strings"
- "time"
- "fwh-toolbox-server/cache"
- minioRepo "fwh-toolbox-server/repositories/minio"
- mongoRepo "fwh-toolbox-server/repositories/mongo"
- )
- type DataDownloadArchiveEntry struct {
- Path string `json:"path"`
- Size int64 `json:"size"`
- Directory bool `json:"directory"`
- }
- func ListDataDownloadArchive(ctx context.Context, documentID string) ([]DataDownloadArchiveEntry, error) {
- lock, err := cache.Lock(ctx, "data-download:content:"+documentID, 10*time.Minute, 100*time.Millisecond, 10*time.Second)
- if err != nil {
- return nil, err
- }
- defer lock.SafeRelease(context.Background())
- reader, compressed, archive, err := openDataDownloadTarGz(ctx, documentID)
- if err != nil {
- return nil, err
- }
- defer reader.Close()
- defer compressed.Close()
- var entries []DataDownloadArchiveEntry
- for {
- header, err := archive.Next()
- if err == io.EOF {
- break
- }
- if err != nil {
- return nil, err
- }
- clean, err := safeArchiveEntry(header.Name)
- if err != nil {
- continue
- }
- entries = append(entries, DataDownloadArchiveEntry{Path: clean, Size: header.Size, Directory: header.FileInfo().IsDir()})
- }
- return entries, nil
- }
- func ReadDataDownloadArchiveEntry(ctx context.Context, documentID, entryPath string) ([]byte, error) {
- lock, err := cache.Lock(ctx, "data-download:content:"+documentID, 10*time.Minute, 100*time.Millisecond, 10*time.Second)
- if err != nil {
- return nil, err
- }
- defer lock.SafeRelease(context.Background())
- wanted, err := safeArchiveEntry(entryPath)
- if err != nil {
- return nil, err
- }
- reader, compressed, archive, err := openDataDownloadTarGz(ctx, documentID)
- if err != nil {
- return nil, err
- }
- defer reader.Close()
- defer compressed.Close()
- for {
- header, err := archive.Next()
- if err == io.EOF {
- return nil, fmt.Errorf("archive entry not found")
- }
- if err != nil {
- return nil, err
- }
- clean, cleanErr := safeArchiveEntry(header.Name)
- if cleanErr != nil || clean != wanted || header.FileInfo().IsDir() {
- continue
- }
- return io.ReadAll(io.LimitReader(archive, header.Size))
- }
- }
- func openDataDownloadTarGz(ctx context.Context, documentID string) (io.ReadCloser, *gzip.Reader, *tar.Reader, error) {
- document, err := mongoRepo.GetDataDownloadDocument(ctx, documentID)
- if err != nil {
- return nil, nil, nil, err
- }
- if document.BinaryObjectKey == "" || !(strings.HasSuffix(strings.ToLower(document.BinaryObjectKey), ".tar.gz") || strings.HasSuffix(strings.ToLower(document.BinaryObjectKey), ".tgz")) {
- return nil, nil, nil, fmt.Errorf("document is not a tar.gz archive")
- }
- reader, err := minioRepo.Open(ctx, document.BinaryObjectKey)
- if err != nil {
- return nil, nil, nil, err
- }
- gz, err := gzip.NewReader(reader)
- if err != nil {
- _ = reader.Close()
- return nil, nil, nil, err
- }
- return reader, gz, tar.NewReader(gz), nil
- }
- func safeArchiveEntry(value string) (string, error) {
- clean := strings.TrimPrefix(path.Clean("/"+strings.ReplaceAll(value, "\\", "/")), "/")
- if clean == "." || clean == "" || strings.HasPrefix(clean, "../") {
- return "", fmt.Errorf("invalid archive entry")
- }
- return clean, nil
- }
|