forked from mystiq/dex
137 lines
3.1 KiB
Go
137 lines
3.1 KiB
Go
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
|
|
}
|