1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package magic
18
19 import (
20 "archive/tar"
21 "archive/zip"
22 "bufio"
23 "bytes"
24 "compress/gzip"
25 "encoding/binary"
26 "errors"
27 "io"
28 "os"
29 "path"
30 "strings"
31
32 "github.com/xi2/xz"
33 )
34
35 type FileType int
36 type CompressionType int
37
38 const (
39 FileTypeUnknown FileType = iota
40 FileTypeRPM
41 FileTypeDEB
42 FileTypePGP
43 FileTypeJAR
44 FileTypePKCS7
45 FileTypePECOFF
46 FileTypeMSI
47 FileTypeCAB
48 FileTypeAppManifest
49 FileTypeCAT
50 FileTypeStarman
51 FileTypeAPPX
52 FileTypeVSIX
53 FileTypeXAP
54 FileTypeAPK
55 )
56
57 const (
58 CompressedNone CompressionType = iota
59 CompressedGzip
60 CompressedXz
61 )
62
63 func hasPrefix(br *bufio.Reader, blob []byte) bool {
64 return atPosition(br, blob, 0)
65 }
66
67 func contains(br *bufio.Reader, blob []byte, n int) bool {
68 d, _ := br.Peek(n)
69 if len(d) < len(blob) {
70 return false
71 }
72 return bytes.Contains(d, blob)
73 }
74
75 func atPosition(br *bufio.Reader, blob []byte, n int) bool {
76 l := n + len(blob)
77 d, _ := br.Peek(l)
78 if len(d) < l {
79 return false
80 }
81 return bytes.Equal(d[n:], blob)
82 }
83
84
85
86 func Detect(r io.Reader) FileType {
87 br := bufio.NewReader(r)
88 switch {
89 case hasPrefix(br, []byte{0xed, 0xab, 0xee, 0xdb}):
90 return FileTypeRPM
91 case hasPrefix(br, []byte("!<arch>\ndebian")):
92 return FileTypeDEB
93 case hasPrefix(br, []byte("-----BEGIN PGP")):
94 return FileTypePGP
95 case contains(br, []byte{0x06, 0x09, 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x0A, 0x01}, 256):
96
97 return FileTypeCAT
98 case contains(br, []byte{0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02}, 256):
99
100 return FileTypePKCS7
101 case isTar(br):
102 return detectTar(br)
103 case hasPrefix(br, []byte("MZ")):
104 if blob, _ := br.Peek(0x3e); len(blob) == 0x3e {
105 reloc := binary.LittleEndian.Uint16(blob[0x3c:0x3e])
106 if blob, err := br.Peek(int(reloc) + 4); err == nil {
107 if bytes.Equal(blob[reloc:reloc+4], []byte("PE\x00\x00")) {
108 return FileTypePECOFF
109 }
110 }
111 }
112 case hasPrefix(br, []byte{0xd0, 0xcf}):
113 return FileTypeMSI
114 case hasPrefix(br, []byte("MSCF")):
115 return FileTypeCAB
116 case contains(br, []byte("<assembly"), 256),
117 contains(br, []byte(":assembly"), 256):
118 return FileTypeAppManifest
119 case hasPrefix(br, []byte{0x89}), hasPrefix(br, []byte{0xc2}), hasPrefix(br, []byte{0xc4}):
120 return FileTypePGP
121 }
122 return FileTypeUnknown
123 }
124
125 func DetectCompressed(f *os.File) (FileType, CompressionType) {
126 br := bufio.NewReader(f)
127 ftype := FileTypeUnknown
128 switch {
129 case hasPrefix(br, []byte{0x1f, 0x8b}):
130 zr, err := gzip.NewReader(br)
131 if err == nil {
132 zbr := bufio.NewReader(zr)
133 if isTar(zbr) {
134 ftype = detectTar(zbr)
135 }
136 }
137 return ftype, CompressedGzip
138 case hasPrefix(br, []byte("\xfd7zXZ\x00")):
139 zr, err := xz.NewReader(br, 0)
140 if err == nil {
141 zbr := bufio.NewReader(zr)
142 if isTar(zbr) {
143 ftype = detectTar(zbr)
144 }
145 }
146 return ftype, CompressedXz
147 case hasPrefix(br, []byte{0x50, 0x4b, 0x03, 0x04}):
148 return detectZip(f), CompressedNone
149 }
150 return Detect(br), CompressedNone
151 }
152
153 func Decompress(r io.Reader, ctype CompressionType) (io.Reader, error) {
154 switch ctype {
155 case CompressedNone:
156 return r, nil
157 case CompressedGzip:
158 return gzip.NewReader(r)
159 case CompressedXz:
160 return xz.NewReader(r, 0)
161 default:
162 return nil, errors.New("invalid compression type")
163 }
164 }
165
166 func isTar(br *bufio.Reader) bool {
167 return atPosition(br, []byte("ustar"), 257)
168 }
169
170 func detectTar(r io.Reader) FileType {
171 hdr, err := tar.NewReader(r).Next()
172 if err != nil {
173 return FileTypeUnknown
174 }
175 switch {
176 case strings.HasPrefix(hdr.Name, ".metadata/") && strings.HasSuffix(hdr.Name, ".meta"):
177 return FileTypeStarman
178 }
179 return FileTypeUnknown
180 }
181
182 func detectZip(f *os.File) FileType {
183 size, err := f.Seek(0, io.SeekEnd)
184 if err != nil {
185 return FileTypeUnknown
186 }
187 inz, err := zip.NewReader(f, size)
188 if err != nil {
189 return FileTypeUnknown
190 }
191 var isJar bool
192 for _, zf := range inz.File {
193 name := zf.Name
194 if strings.HasPrefix(name, "/") {
195 name = "." + name
196 }
197 name = path.Clean(name)
198 switch zf.Name {
199 case "AndroidManifest.xml":
200 return FileTypeAPK
201 case "AppManifest.xaml":
202 return FileTypeXAP
203 case "AppxManifest.xml", "AppxMetadata/AppxBundleManifest.xml":
204 return FileTypeAPPX
205 case "extension.vsixmanifest":
206 return FileTypeVSIX
207 case "META-INF/MANIFEST.MF":
208
209 isJar = true
210 }
211 }
212 if isJar {
213 return FileTypeJAR
214 }
215 return FileTypeUnknown
216 }
217
View as plain text