ExtractFileFromTar extracts a single file from a tar archive. Uses Go's standard archive/tar for cross-platform compatibility instead of spawning an external tar process which may not be available on all platforms. path must be a local, relative path (no absolute paths or ".." components). filepath
(data []byte, path string)
| 21 | // filepath.IsLocal is used to enforce this for both the search target and each |
| 22 | // tar entry name, guarding against path-traversal payloads embedded in archives. |
| 23 | func ExtractFileFromTar(data []byte, path string) ([]byte, error) { |
| 24 | // Reject unsafe search targets before opening the archive. |
| 25 | if !filepath.IsLocal(path) { |
| 26 | return nil, fmt.Errorf("unsafe path requested from tar archive: %q", path) |
| 27 | } |
| 28 | |
| 29 | tarLog.Printf("Extracting file from tar archive: target=%s, archive_size=%d bytes", path, len(data)) |
| 30 | tr := tar.NewReader(bytes.NewReader(data)) |
| 31 | entriesScanned := 0 |
| 32 | for { |
| 33 | header, err := tr.Next() |
| 34 | if errors.Is(err, io.EOF) { |
| 35 | tarLog.Printf("File not found in tar archive after scanning %d entries: %s", entriesScanned, path) |
| 36 | break |
| 37 | } |
| 38 | if err != nil { |
| 39 | return nil, fmt.Errorf("failed to read tar archive: %w", err) |
| 40 | } |
| 41 | entriesScanned++ |
| 42 | // Reject tar entries that could escape a destination directory. |
| 43 | if !filepath.IsLocal(header.Name) { |
| 44 | tarLog.Printf("Skipping unsafe tar entry: %s", header.Name) |
| 45 | continue |
| 46 | } |
| 47 | if header.Name == path { |
| 48 | tarLog.Printf("Found file in tar archive after scanning %d entries: %s", entriesScanned, path) |
| 49 | return io.ReadAll(tr) |
| 50 | } |
| 51 | } |
| 52 | return nil, fmt.Errorf("file %q not found in archive", path) |
| 53 | } |