...

Source file src/github.com/sassoftware/relic/lib/magic/magic.go

Documentation: github.com/sassoftware/relic/lib/magic

     1  //
     2  // Copyright (c) SAS Institute 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 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  // Detect a handful of package and signature file types based on the first few
    85  // bytes of the file contents.
    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  		// OID certTrustList
    97  		return FileTypeCAT
    98  	case contains(br, []byte{0x06, 0x09, 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02}, 256):
    99  		// OID signedData
   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  			// APKs are also JARs so save this for last
   209  			isJar = true
   210  		}
   211  	}
   212  	if isJar {
   213  		return FileTypeJAR
   214  	}
   215  	return FileTypeUnknown
   216  }
   217  

View as plain text