storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/cmd/untar.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2021 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package cmd 18 19 import ( 20 "archive/tar" 21 "bufio" 22 "bytes" 23 "compress/bzip2" 24 "fmt" 25 "io" 26 "os" 27 "path" 28 29 "github.com/klauspost/compress/s2" 30 "github.com/klauspost/compress/zstd" 31 gzip "github.com/klauspost/pgzip" 32 "github.com/pierrec/lz4" 33 ) 34 35 func detect(r *bufio.Reader) format { 36 z, err := r.Peek(4) 37 if err != nil { 38 return formatUnknown 39 } 40 for _, f := range magicHeaders { 41 if bytes.Equal(f.header, z[:len(f.header)]) { 42 return f.f 43 } 44 } 45 return formatUnknown 46 } 47 48 //go:generate stringer -type=format -trimprefix=format $GOFILE 49 type format int 50 51 const ( 52 formatUnknown format = iota 53 formatGzip 54 formatZstd 55 formatLZ4 56 formatS2 57 formatBZ2 58 ) 59 60 var magicHeaders = []struct { 61 header []byte 62 f format 63 }{ 64 { 65 header: []byte{0x1f, 0x8b, 8}, 66 f: formatGzip, 67 }, 68 { 69 // Zstd default header. 70 header: []byte{0x28, 0xb5, 0x2f, 0xfd}, 71 f: formatZstd, 72 }, 73 { 74 // Zstd skippable frame header. 75 header: []byte{0x2a, 0x4d, 0x18}, 76 f: formatZstd, 77 }, 78 { 79 // LZ4 80 header: []byte{0x4, 0x22, 0x4d, 0x18}, 81 f: formatLZ4, 82 }, 83 { 84 // Snappy/S2 stream 85 header: []byte{0xff, 0x06, 0x00, 0x00}, 86 f: formatS2, 87 }, 88 { 89 header: []byte{0x42, 0x5a, 'h'}, 90 f: formatBZ2, 91 }, 92 } 93 94 func untar(r io.Reader, putObject func(reader io.Reader, info os.FileInfo, name string)) error { 95 bf := bufio.NewReader(r) 96 switch f := detect(bf); f { 97 case formatGzip: 98 gz, err := gzip.NewReader(bf) 99 if err != nil { 100 return err 101 } 102 defer gz.Close() 103 r = gz 104 case formatS2: 105 r = s2.NewReader(bf) 106 case formatZstd: 107 dec, err := zstd.NewReader(bf) 108 if err != nil { 109 return err 110 } 111 defer dec.Close() 112 r = dec 113 case formatBZ2: 114 r = bzip2.NewReader(bf) 115 case formatLZ4: 116 r = lz4.NewReader(bf) 117 case formatUnknown: 118 r = bf 119 default: 120 return fmt.Errorf("Unsupported format %s", f) 121 } 122 tarReader := tar.NewReader(r) 123 for { 124 header, err := tarReader.Next() 125 126 switch { 127 128 // if no more files are found return 129 case err == io.EOF: 130 return nil 131 132 // return any other error 133 case err != nil: 134 return err 135 136 // if the header is nil, just skip it (not sure how this happens) 137 case header == nil: 138 continue 139 } 140 141 name := header.Name 142 if name == slashSeparator { 143 continue 144 } 145 146 switch header.Typeflag { 147 case tar.TypeDir: // = directory 148 putObject(tarReader, header.FileInfo(), trimLeadingSlash(pathJoin(name, slashSeparator))) 149 case tar.TypeReg, tar.TypeChar, tar.TypeBlock, tar.TypeFifo, tar.TypeGNUSparse: // = regular 150 putObject(tarReader, header.FileInfo(), trimLeadingSlash(path.Clean(name))) 151 default: 152 // ignore symlink'ed 153 continue 154 } 155 } 156 }