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 ea, nb, err } 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, nb, err } 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 ea, nb, err } // 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 eas, err } 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 }