| 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 | |
| } |