- Implement TNEF extraction and recursive parsing in new `tnef_reader.go` and associated tests. - Create tests for TNEF extraction scenarios in `tnef_diag_test.go`, `tnef_diag7_test.go`, and `tnef_diag8_test.go`.
242 lines
6.4 KiB
Go
242 lines
6.4 KiB
Go
package internal
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestTNEFMapiProps(t *testing.T) {
|
|
testFile := `H:\Dev\Gits\EMLy\EML_TNEF.eml`
|
|
if _, err := os.Stat(testFile); os.IsNotExist(err) {
|
|
t.Skip("test EML file not present")
|
|
}
|
|
|
|
f, _ := os.Open(testFile)
|
|
defer f.Close()
|
|
|
|
outerEmail, _ := Parse(f)
|
|
var innerData []byte
|
|
for _, att := range outerEmail.Attachments {
|
|
if strings.Contains(strings.ToLower(att.Filename), "postacert.eml") {
|
|
innerData, _ = io.ReadAll(att.Data)
|
|
break
|
|
}
|
|
}
|
|
|
|
innerEmail, _ := Parse(bytes.NewReader(innerData))
|
|
for _, att := range innerEmail.Attachments {
|
|
rawData, _ := io.ReadAll(att.Data)
|
|
if strings.ToLower(att.Filename) != "winmail.dat" {
|
|
continue
|
|
}
|
|
|
|
// Navigate to the first attachment's attAttachment (0x9005) block
|
|
// From the raw scan: [011] offset=12082 + header(9bytes) = 12091 for data
|
|
// Actually let's re-scan to find it properly
|
|
offset := 6
|
|
for offset < len(rawData) {
|
|
if offset+9 > len(rawData) {
|
|
break
|
|
}
|
|
level := rawData[offset]
|
|
attrID := binary.LittleEndian.Uint32(rawData[offset+1 : offset+5])
|
|
attrLen := int(binary.LittleEndian.Uint32(rawData[offset+5 : offset+9]))
|
|
dataStart := offset + 9
|
|
|
|
// attAttachment = 0x00069005, we want the FIRST one (for attachment group 1)
|
|
if level == 0x02 && attrID == 0x00069005 && attrLen > 1000 {
|
|
fmt.Printf("Found attAttachment at offset %d, len=%d\n", offset, attrLen)
|
|
parseMapiProps(rawData[dataStart:dataStart+attrLen], t)
|
|
break
|
|
}
|
|
|
|
offset += 9 + attrLen + 2
|
|
}
|
|
}
|
|
}
|
|
|
|
func parseMapiProps(data []byte, t *testing.T) {
|
|
if len(data) < 4 {
|
|
t.Fatal("too short for MAPI props")
|
|
}
|
|
|
|
count := binary.LittleEndian.Uint32(data[0:4])
|
|
fmt.Printf("MAPI property count: %d\n", count)
|
|
|
|
offset := 4
|
|
for i := 0; i < int(count) && offset+4 <= len(data); i++ {
|
|
propTag := binary.LittleEndian.Uint32(data[offset : offset+4])
|
|
propType := propTag & 0xFFFF
|
|
propID := (propTag >> 16) & 0xFFFF
|
|
offset += 4
|
|
|
|
// Handle named properties (ID >= 0x8000)
|
|
if propID >= 0x8000 {
|
|
// Skip GUID (16 bytes) + kind (4 bytes)
|
|
if offset+20 > len(data) {
|
|
break
|
|
}
|
|
kind := binary.LittleEndian.Uint32(data[offset+16 : offset+20])
|
|
offset += 20
|
|
if kind == 0 { // MNID_ID
|
|
offset += 4 // skip NamedID
|
|
} else { // MNID_STRING
|
|
if offset+4 > len(data) {
|
|
break
|
|
}
|
|
nameLen := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4 + nameLen
|
|
// Pad to 4-byte boundary
|
|
if nameLen%4 != 0 {
|
|
offset += 4 - nameLen%4
|
|
}
|
|
}
|
|
}
|
|
|
|
var valueSize int
|
|
switch propType {
|
|
case 0x0002: // PT_SHORT
|
|
valueSize = 4 // padded to 4
|
|
case 0x0003: // PT_LONG
|
|
valueSize = 4
|
|
case 0x000B: // PT_BOOLEAN
|
|
valueSize = 4
|
|
case 0x0040: // PT_SYSTIME
|
|
valueSize = 8
|
|
case 0x001E: // PT_STRING8
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
// count=1, then length, then data padded
|
|
cnt := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
for j := 0; j < cnt; j++ {
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
slen := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
strData := ""
|
|
if offset+slen <= len(data) && slen < 200 {
|
|
strData = string(data[offset : offset+slen])
|
|
}
|
|
fmt.Printf(" [%03d] PropID=0x%04X Type=0x%04X STRING8 len=%d val=%q\n", i, propID, propType, slen, strData)
|
|
offset += slen
|
|
if slen%4 != 0 {
|
|
offset += 4 - slen%4
|
|
}
|
|
}
|
|
continue
|
|
case 0x001F: // PT_UNICODE
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
cnt := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
for j := 0; j < cnt; j++ {
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
slen := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
fmt.Printf(" [%03d] PropID=0x%04X Type=0x%04X UNICODE len=%d\n", i, propID, propType, slen)
|
|
offset += slen
|
|
if slen%4 != 0 {
|
|
offset += 4 - slen%4
|
|
}
|
|
}
|
|
continue
|
|
case 0x0102: // PT_BINARY
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
cnt := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
for j := 0; j < cnt; j++ {
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
blen := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
fmt.Printf(" [%03d] PropID=0x%04X Type=0x%04X BINARY len=%d\n", i, propID, propType, blen)
|
|
offset += blen
|
|
if blen%4 != 0 {
|
|
offset += 4 - blen%4
|
|
}
|
|
}
|
|
continue
|
|
case 0x000D: // PT_OBJECT
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
cnt := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
for j := 0; j < cnt; j++ {
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
olen := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
fmt.Printf(" [%03d] PropID=0x%04X Type=0x%04X OBJECT len=%d\n", i, propID, propType, olen)
|
|
// Peek at first 16 bytes (GUID)
|
|
if offset+16 <= len(data) {
|
|
fmt.Printf(" GUID: %x\n", data[offset:offset+16])
|
|
}
|
|
offset += olen
|
|
if olen%4 != 0 {
|
|
offset += 4 - olen%4
|
|
}
|
|
}
|
|
continue
|
|
case 0x1003: // PT_MV_LONG
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
cnt := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
fmt.Printf(" [%03d] PropID=0x%04X Type=0x%04X MV_LONG count=%d\n", i, propID, propType, cnt)
|
|
offset += cnt * 4
|
|
continue
|
|
case 0x1102: // PT_MV_BINARY
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
cnt := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
totalSize := 0
|
|
for j := 0; j < cnt; j++ {
|
|
if offset+4 > len(data) {
|
|
return
|
|
}
|
|
blen := int(binary.LittleEndian.Uint32(data[offset : offset+4]))
|
|
offset += 4
|
|
totalSize += blen
|
|
offset += blen
|
|
if blen%4 != 0 {
|
|
offset += 4 - blen%4
|
|
}
|
|
}
|
|
fmt.Printf(" [%03d] PropID=0x%04X Type=0x%04X MV_BINARY count=%d totalSize=%d\n", i, propID, propType, cnt, totalSize)
|
|
continue
|
|
default:
|
|
fmt.Printf(" [%03d] PropID=0x%04X Type=0x%04X (unknown type)\n", i, propID, propType)
|
|
return
|
|
}
|
|
|
|
if valueSize > 0 {
|
|
if propType == 0x0003 && offset+4 <= len(data) {
|
|
val := binary.LittleEndian.Uint32(data[offset : offset+4])
|
|
fmt.Printf(" [%03d] PropID=0x%04X Type=0x%04X LONG val=%d (0x%X)\n", i, propID, propType, val, val)
|
|
} else {
|
|
fmt.Printf(" [%03d] PropID=0x%04X Type=0x%04X size=%d\n", i, propID, propType, valueSize)
|
|
}
|
|
offset += valueSize
|
|
}
|
|
}
|
|
}
|