11package archive
22
33import (
4+ "archive/tar"
45 "context"
56 "fmt"
7+ "io"
68 "io/fs"
79 "os"
810 "path/filepath"
@@ -190,13 +192,16 @@ func ExtractArchiveToTempDir(ctx context.Context, path string) (string, error) {
190192}
191193
192194func ExtractionMethod (ext string ) func (context.Context , string , string ) error {
195+ // The ordering of these statements is important, especially for extensions
196+ // that are substrings of other extensions (e.g., `.gz` and `.tar.gz` or `.tgz`)
193197 switch ext {
194- case ".jar" , ".zip" , ".whl" :
195- return ExtractZip
196- case ".gz" :
197- return ExtractGzip
198+ // New cases should go below this line so that the lengthier tar extensions are evaluated first
198199 case ".apk" , ".gem" , ".tar" , ".tar.bz2" , ".tar.gz" , ".tgz" , ".tar.xz" , ".tbz" , ".xz" :
199200 return ExtractTar
201+ case ".gz" , ".gzip" :
202+ return ExtractGzip
203+ case ".jar" , ".zip" , ".whl" :
204+ return ExtractZip
200205 case ".bz2" , ".bzip2" :
201206 return ExtractBz2
202207 case ".rpm" :
@@ -207,3 +212,68 @@ func ExtractionMethod(ext string) func(context.Context, string, string) error {
207212 return nil
208213 }
209214}
215+
216+ // handleDirectory extracts valid directories within .deb or .tar archives.
217+ func handleDirectory (target string ) error {
218+ if err := os .MkdirAll (target , 0o700 ); err != nil {
219+ return fmt .Errorf ("failed to create directory: %w" , err )
220+ }
221+ return nil
222+ }
223+
224+ // handleFile extracts valid files within .deb or .tar archives.
225+ func handleFile (target string , tr * tar.Reader ) error {
226+ if err := os .MkdirAll (filepath .Dir (target ), 0o700 ); err != nil {
227+ return fmt .Errorf ("failed to create parent directory: %w" , err )
228+ }
229+
230+ out , err := os .OpenFile (target , os .O_WRONLY | os .O_CREATE | os .O_TRUNC , 0o600 )
231+ if err != nil {
232+ return fmt .Errorf ("failed to create file: %w" , err )
233+ }
234+ defer out .Close ()
235+
236+ written , err := io .Copy (out , io .LimitReader (tr , maxBytes ))
237+ if err != nil {
238+ return fmt .Errorf ("failed to copy file: %w" , err )
239+ }
240+ if written >= maxBytes {
241+ return fmt .Errorf ("file exceeds maximum allowed size (%d bytes): %s" , maxBytes , target )
242+ }
243+
244+ return nil
245+ }
246+
247+ // handleSymlink creates valid symlinks when extracting .deb or .tar archives.
248+ func handleSymlink (dir , linkName , target string ) error {
249+ // Skip symlinks for targets that do not exist
250+ _ , err := os .Readlink (target )
251+ if os .IsNotExist (err ) {
252+ return nil
253+ }
254+
255+ fullLink := filepath .Join (dir , linkName )
256+
257+ // Remove existing symlinks
258+ if _ , err := os .Lstat (fullLink ); err == nil {
259+ if err := os .Remove (fullLink ); err != nil {
260+ return fmt .Errorf ("failed to remove existing symlink: %w" , err )
261+ }
262+ }
263+
264+ if err := os .Symlink (target , fullLink ); err != nil {
265+ return fmt .Errorf ("failed to create symlink: %w" , err )
266+ }
267+
268+ linkReal , err := filepath .EvalSymlinks (fullLink )
269+ if err != nil {
270+ os .Remove (fullLink )
271+ return fmt .Errorf ("failed to evaluate symlink: %w" , err )
272+ }
273+ if ! IsValidPath (linkReal , dir ) {
274+ os .Remove (fullLink )
275+ return fmt .Errorf ("symlink points outside temporary directory: %s" , linkReal )
276+ }
277+
278+ return nil
279+ }
0 commit comments