package winio | |
import ( | |
"bytes" | |
"encoding/binary" | |
"errors" | |
) | |
type fileFullEaInformation struct { | |
NextEntryOffset uint32 | |
Flags uint8 | |
NameLength uint8 | |
ValueLength uint16 | |
} | |
var ( | |
fileFullEaInformationSize = binary.Size(&fileFullEaInformation{}) | |
errInvalidEaBuffer = errors.New("invalid extended attribute buffer") | |
errEaNameTooLarge = errors.New("extended attribute name too large") | |
errEaValueTooLarge = errors.New("extended attribute value too large") | |
) | |
// ExtendedAttribute represents a single Windows EA. | |
type ExtendedAttribute struct { | |
Name string | |
Value []byte | |
Flags uint8 | |
} | |
func parseEa(b []byte) (ea ExtendedAttribute, nb []byte, err error) { | |
var info fileFullEaInformation | |
err = binary.Read(bytes.NewReader(b), binary.LittleEndian, &info) | |
if err != nil { | |
err = errInvalidEaBuffer | |
return | |
} | |
nameOffset := fileFullEaInformationSize | |
nameLen := int(info.NameLength) | |
valueOffset := nameOffset + int(info.NameLength) + 1 | |
valueLen := int(info.ValueLength) | |
nextOffset := int(info.NextEntryOffset) | |
if valueLen+valueOffset > len(b) || nextOffset < 0 || nextOffset > len(b) { | |
err = errInvalidEaBuffer | |
return | |
} | |
ea.Name = string(b[nameOffset : nameOffset+nameLen]) | |
ea.Value = b[valueOffset : valueOffset+valueLen] | |
ea.Flags = info.Flags | |
if info.NextEntryOffset != 0 { | |
nb = b[info.NextEntryOffset:] | |
} | |
return | |
} | |
// DecodeExtendedAttributes decodes a list of EAs from a FILE_FULL_EA_INFORMATION | |
// buffer retrieved from BackupRead, ZwQueryEaFile, etc. | |
func DecodeExtendedAttributes(b []byte) (eas []ExtendedAttribute, err error) { | |
for len(b) != 0 { | |
ea, nb, err := parseEa(b) | |
if err != nil { | |
return nil, err | |
} | |
eas = append(eas, ea) | |
b = nb | |
} | |
return | |
} | |
func writeEa(buf *bytes.Buffer, ea *ExtendedAttribute, last bool) error { | |
if int(uint8(len(ea.Name))) != len(ea.Name) { | |
return errEaNameTooLarge | |
} | |
if int(uint16(len(ea.Value))) != len(ea.Value) { | |
return errEaValueTooLarge | |
} | |
entrySize := uint32(fileFullEaInformationSize + len(ea.Name) + 1 + len(ea.Value)) | |
withPadding := (entrySize + 3) &^ 3 | |
nextOffset := uint32(0) | |
if !last { | |
nextOffset = withPadding | |
} | |
info := fileFullEaInformation{ | |
NextEntryOffset: nextOffset, | |
Flags: ea.Flags, | |
NameLength: uint8(len(ea.Name)), | |
ValueLength: uint16(len(ea.Value)), | |
} | |
err := binary.Write(buf, binary.LittleEndian, &info) | |
if err != nil { | |
return err | |
} | |
_, err = buf.Write([]byte(ea.Name)) | |
if err != nil { | |
return err | |
} | |
err = buf.WriteByte(0) | |
if err != nil { | |
return err | |
} | |
_, err = buf.Write(ea.Value) | |
if err != nil { | |
return err | |
} | |
_, err = buf.Write([]byte{0, 0, 0}[0 : withPadding-entrySize]) | |
if err != nil { | |
return err | |
} | |
return nil | |
} | |
// EncodeExtendedAttributes encodes a list of EAs into a FILE_FULL_EA_INFORMATION | |
// buffer for use with BackupWrite, ZwSetEaFile, etc. | |
func EncodeExtendedAttributes(eas []ExtendedAttribute) ([]byte, error) { | |
var buf bytes.Buffer | |
for i := range eas { | |
last := false | |
if i == len(eas)-1 { | |
last = true | |
} | |
err := writeEa(&buf, &eas[i], last) | |
if err != nil { | |
return nil, err | |
} | |
} | |
return buf.Bytes(), nil | |
} |