...

Source file src/github.com/gabriel-vasile/mimetype/internal/magic/ms_office.go

Documentation: github.com/gabriel-vasile/mimetype/internal/magic

     1  package magic
     2  
     3  import (
     4  	"bytes"
     5  	"encoding/binary"
     6  )
     7  
     8  var (
     9  	xlsxSigFiles = []string{
    10  		"xl/worksheets/",
    11  		"xl/drawings/",
    12  		"xl/theme/",
    13  		"xl/_rels/",
    14  		"xl/styles.xml",
    15  		"xl/workbook.xml",
    16  		"xl/sharedStrings.xml",
    17  	}
    18  	docxSigFiles = []string{
    19  		"word/media/",
    20  		"word/_rels/document.xml.rels",
    21  		"word/document.xml",
    22  		"word/styles.xml",
    23  		"word/fontTable.xml",
    24  		"word/settings.xml",
    25  		"word/numbering.xml",
    26  		"word/header",
    27  		"word/footer",
    28  	}
    29  	pptxSigFiles = []string{
    30  		"ppt/slides/",
    31  		"ppt/media/",
    32  		"ppt/slideLayouts/",
    33  		"ppt/theme/",
    34  		"ppt/slideMasters/",
    35  		"ppt/tags/",
    36  		"ppt/notesMasters/",
    37  		"ppt/_rels/",
    38  		"ppt/handoutMasters/",
    39  		"ppt/notesSlides/",
    40  		"ppt/presentation.xml",
    41  		"ppt/tableStyles.xml",
    42  		"ppt/presProps.xml",
    43  		"ppt/viewProps.xml",
    44  	}
    45  )
    46  
    47  // Xlsx matches a Microsoft Excel 2007 file.
    48  func Xlsx(raw []byte, limit uint32) bool {
    49  	return zipContains(raw, xlsxSigFiles...)
    50  }
    51  
    52  // Docx matches a Microsoft Word 2007 file.
    53  func Docx(raw []byte, limit uint32) bool {
    54  	return zipContains(raw, docxSigFiles...)
    55  }
    56  
    57  // Pptx matches a Microsoft PowerPoint 2007 file.
    58  func Pptx(raw []byte, limit uint32) bool {
    59  	return zipContains(raw, pptxSigFiles...)
    60  }
    61  
    62  // Ole matches an Open Linking and Embedding file.
    63  //
    64  // https://en.wikipedia.org/wiki/Object_Linking_and_Embedding
    65  func Ole(raw []byte, limit uint32) bool {
    66  	return bytes.HasPrefix(raw, []byte{0xD0, 0xCF, 0x11, 0xE0, 0xA1, 0xB1, 0x1A, 0xE1})
    67  }
    68  
    69  // Aaf matches an Advanced Authoring Format file.
    70  // See: https://pyaaf.readthedocs.io/en/latest/about.html
    71  // See: https://en.wikipedia.org/wiki/Advanced_Authoring_Format
    72  func Aaf(raw []byte, limit uint32) bool {
    73  	if len(raw) < 31 {
    74  		return false
    75  	}
    76  	return bytes.HasPrefix(raw[8:], []byte{0x41, 0x41, 0x46, 0x42, 0x0D, 0x00, 0x4F, 0x4D}) &&
    77  		(raw[30] == 0x09 || raw[30] == 0x0C)
    78  }
    79  
    80  // Doc matches a Microsoft Word 97-2003 file.
    81  // See: https://github.com/decalage2/oletools/blob/412ee36ae45e70f42123e835871bac956d958461/oletools/common/clsid.py
    82  func Doc(raw []byte, _ uint32) bool {
    83  	clsids := [][]byte{
    84  		// Microsoft Word 97-2003 Document (Word.Document.8)
    85  		{0x06, 0x09, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46},
    86  		// Microsoft Word 6.0-7.0 Document (Word.Document.6)
    87  		{0x00, 0x09, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46},
    88  		// Microsoft Word Picture (Word.Picture.8)
    89  		{0x07, 0x09, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46},
    90  	}
    91  
    92  	for _, clsid := range clsids {
    93  		if matchOleClsid(raw, clsid) {
    94  			return true
    95  		}
    96  	}
    97  
    98  	return false
    99  }
   100  
   101  // Ppt matches a Microsoft PowerPoint 97-2003 file or a PowerPoint 95 presentation.
   102  func Ppt(raw []byte, limit uint32) bool {
   103  	// Root CLSID test is the safest way to detect identify OLE, however, the format
   104  	// often places the root CLSID at the end of the file.
   105  	if matchOleClsid(raw, []byte{
   106  		0x10, 0x8d, 0x81, 0x64, 0x9b, 0x4f, 0xcf, 0x11,
   107  		0x86, 0xea, 0x00, 0xaa, 0x00, 0xb9, 0x29, 0xe8,
   108  	}) || matchOleClsid(raw, []byte{
   109  		0x70, 0xae, 0x7b, 0xea, 0x3b, 0xfb, 0xcd, 0x11,
   110  		0xa9, 0x03, 0x00, 0xaa, 0x00, 0x51, 0x0e, 0xa3,
   111  	}) {
   112  		return true
   113  	}
   114  
   115  	lin := len(raw)
   116  	if lin < 520 {
   117  		return false
   118  	}
   119  	pptSubHeaders := [][]byte{
   120  		{0xA0, 0x46, 0x1D, 0xF0},
   121  		{0x00, 0x6E, 0x1E, 0xF0},
   122  		{0x0F, 0x00, 0xE8, 0x03},
   123  	}
   124  	for _, h := range pptSubHeaders {
   125  		if bytes.HasPrefix(raw[512:], h) {
   126  			return true
   127  		}
   128  	}
   129  
   130  	if bytes.HasPrefix(raw[512:], []byte{0xFD, 0xFF, 0xFF, 0xFF}) &&
   131  		raw[518] == 0x00 && raw[519] == 0x00 {
   132  		return true
   133  	}
   134  
   135  	return lin > 1152 && bytes.Contains(raw[1152:min(4096, lin)],
   136  		[]byte("P\x00o\x00w\x00e\x00r\x00P\x00o\x00i\x00n\x00t\x00 D\x00o\x00c\x00u\x00m\x00e\x00n\x00t"))
   137  }
   138  
   139  // Xls matches a Microsoft Excel 97-2003 file.
   140  func Xls(raw []byte, limit uint32) bool {
   141  	// Root CLSID test is the safest way to detect identify OLE, however, the format
   142  	// often places the root CLSID at the end of the file.
   143  	if matchOleClsid(raw, []byte{
   144  		0x10, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
   145  	}) || matchOleClsid(raw, []byte{
   146  		0x20, 0x08, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
   147  	}) {
   148  		return true
   149  	}
   150  
   151  	lin := len(raw)
   152  	if lin < 520 {
   153  		return false
   154  	}
   155  	xlsSubHeaders := [][]byte{
   156  		{0x09, 0x08, 0x10, 0x00, 0x00, 0x06, 0x05, 0x00},
   157  		{0xFD, 0xFF, 0xFF, 0xFF, 0x10},
   158  		{0xFD, 0xFF, 0xFF, 0xFF, 0x1F},
   159  		{0xFD, 0xFF, 0xFF, 0xFF, 0x22},
   160  		{0xFD, 0xFF, 0xFF, 0xFF, 0x23},
   161  		{0xFD, 0xFF, 0xFF, 0xFF, 0x28},
   162  		{0xFD, 0xFF, 0xFF, 0xFF, 0x29},
   163  	}
   164  	for _, h := range xlsSubHeaders {
   165  		if bytes.HasPrefix(raw[512:], h) {
   166  			return true
   167  		}
   168  	}
   169  
   170  	return lin > 1152 && bytes.Contains(raw[1152:min(4096, lin)],
   171  		[]byte("W\x00k\x00s\x00S\x00S\x00W\x00o\x00r\x00k\x00B\x00o\x00o\x00k"))
   172  }
   173  
   174  // Pub matches a Microsoft Publisher file.
   175  func Pub(raw []byte, limit uint32) bool {
   176  	return matchOleClsid(raw, []byte{
   177  		0x01, 0x12, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
   178  		0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
   179  	})
   180  }
   181  
   182  // Msg matches a Microsoft Outlook email file.
   183  func Msg(raw []byte, limit uint32) bool {
   184  	return matchOleClsid(raw, []byte{
   185  		0x0B, 0x0D, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
   186  		0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
   187  	})
   188  }
   189  
   190  // Msi matches a Microsoft Windows Installer file.
   191  // http://fileformats.archiveteam.org/wiki/Microsoft_Compound_File
   192  func Msi(raw []byte, limit uint32) bool {
   193  	return matchOleClsid(raw, []byte{
   194  		0x84, 0x10, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00,
   195  		0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
   196  	})
   197  }
   198  
   199  // Helper to match by a specific CLSID of a compound file.
   200  //
   201  // http://fileformats.archiveteam.org/wiki/Microsoft_Compound_File
   202  func matchOleClsid(in []byte, clsid []byte) bool {
   203  	// Microsoft Compound files v3 have a sector length of 512, while v4 has 4096.
   204  	// Change sector offset depending on file version.
   205  	// https://www.loc.gov/preservation/digital/formats/fdd/fdd000392.shtml
   206  	sectorLength := 512
   207  	if len(in) < sectorLength {
   208  		return false
   209  	}
   210  	if in[26] == 0x04 && in[27] == 0x00 {
   211  		sectorLength = 4096
   212  	}
   213  
   214  	// SecID of first sector of the directory stream.
   215  	firstSecID := int(binary.LittleEndian.Uint32(in[48:52]))
   216  
   217  	// Expected offset of CLSID for root storage object.
   218  	clsidOffset := sectorLength*(1+firstSecID) + 80
   219  
   220  	if len(in) <= clsidOffset+16 {
   221  		return false
   222  	}
   223  
   224  	return bytes.HasPrefix(in[clsidOffset:], clsid)
   225  }
   226  

View as plain text