package filexfer // Attributes related flags. const ( AttrSize = 1 << iota // SSH_FILEXFER_ATTR_SIZE AttrUIDGID // SSH_FILEXFER_ATTR_UIDGID AttrPermissions // SSH_FILEXFER_ATTR_PERMISSIONS AttrACModTime // SSH_FILEXFER_ACMODTIME AttrExtended = 1 << 31 // SSH_FILEXFER_ATTR_EXTENDED ) // Attributes defines the file attributes type defined in draft-ietf-secsh-filexfer-02 // // Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5 type Attributes struct { Flags uint32 // AttrSize Size uint64 // AttrUIDGID UID uint32 GID uint32 // AttrPermissions Permissions FileMode // AttrACmodTime ATime uint32 MTime uint32 // AttrExtended ExtendedAttributes []ExtendedAttribute } // GetSize returns the Size field and a bool that is true if and only if the value is valid/defined. func (a *Attributes) GetSize() (size uint64, ok bool) { return a.Size, a.Flags&AttrSize != 0 } // SetSize is a convenience function that sets the Size field, // and marks the field as valid/defined in Flags. func (a *Attributes) SetSize(size uint64) { a.Flags |= AttrSize a.Size = size } // GetUIDGID returns the UID and GID fields and a bool that is true if and only if the values are valid/defined. func (a *Attributes) GetUIDGID() (uid, gid uint32, ok bool) { return a.UID, a.GID, a.Flags&AttrUIDGID != 0 } // SetUIDGID is a convenience function that sets the UID and GID fields, // and marks the fields as valid/defined in Flags. func (a *Attributes) SetUIDGID(uid, gid uint32) { a.Flags |= AttrUIDGID a.UID = uid a.GID = gid } // GetPermissions returns the Permissions field and a bool that is true if and only if the value is valid/defined. func (a *Attributes) GetPermissions() (perms FileMode, ok bool) { return a.Permissions, a.Flags&AttrPermissions != 0 } // SetPermissions is a convenience function that sets the Permissions field, // and marks the field as valid/defined in Flags. func (a *Attributes) SetPermissions(perms FileMode) { a.Flags |= AttrPermissions a.Permissions = perms } // GetACModTime returns the ATime and MTime fields and a bool that is true if and only if the values are valid/defined. func (a *Attributes) GetACModTime() (atime, mtime uint32, ok bool) { return a.ATime, a.MTime, a.Flags&AttrACModTime != 0 return a.ATime, a.MTime, a.Flags&AttrACModTime != 0 } // SetACModTime is a convenience function that sets the ATime and MTime fields, // and marks the fields as valid/defined in Flags. func (a *Attributes) SetACModTime(atime, mtime uint32) { a.Flags |= AttrACModTime a.ATime = atime a.MTime = mtime } // Len returns the number of bytes a would marshal into. func (a *Attributes) Len() int { length := 4 if a.Flags&AttrSize != 0 { length += 8 } if a.Flags&AttrUIDGID != 0 { length += 4 + 4 } if a.Flags&AttrPermissions != 0 { length += 4 } if a.Flags&AttrACModTime != 0 { length += 4 + 4 } if a.Flags&AttrExtended != 0 { length += 4 for _, ext := range a.ExtendedAttributes { length += ext.Len() } } return length } // MarshalInto marshals e onto the end of the given Buffer. func (a *Attributes) MarshalInto(b *Buffer) { b.AppendUint32(a.Flags) if a.Flags&AttrSize != 0 { b.AppendUint64(a.Size) } if a.Flags&AttrUIDGID != 0 { b.AppendUint32(a.UID) b.AppendUint32(a.GID) } if a.Flags&AttrPermissions != 0 { b.AppendUint32(uint32(a.Permissions)) } if a.Flags&AttrACModTime != 0 { b.AppendUint32(a.ATime) b.AppendUint32(a.MTime) } if a.Flags&AttrExtended != 0 { b.AppendUint32(uint32(len(a.ExtendedAttributes))) for _, ext := range a.ExtendedAttributes { ext.MarshalInto(b) } } } // MarshalBinary returns a as the binary encoding of a. func (a *Attributes) MarshalBinary() ([]byte, error) { buf := NewBuffer(make([]byte, 0, a.Len())) a.MarshalInto(buf) return buf.Bytes(), nil } // UnmarshalFrom unmarshals an Attributes from the given Buffer into e. // // NOTE: The values of fields not covered in the a.Flags are explicitly undefined. func (a *Attributes) UnmarshalFrom(b *Buffer) (err error) { flags, err := b.ConsumeUint32() if err != nil { return err } return a.XXX_UnmarshalByFlags(flags, b) } // XXX_UnmarshalByFlags uses the pre-existing a.Flags field to determine which fields to decode. // DO NOT USE THIS: it is an anti-corruption function to implement existing internal usage in pkg/sftp. // This function is not a part of any compatibility promise. func (a *Attributes) XXX_UnmarshalByFlags(flags uint32, b *Buffer) (err error) { a.Flags = flags // Short-circuit dummy attributes. if a.Flags == 0 { return nil } if a.Flags&AttrSize != 0 { if a.Size, err = b.ConsumeUint64(); err != nil { return err } } if a.Flags&AttrUIDGID != 0 { if a.UID, err = b.ConsumeUint32(); err != nil { return err } if a.GID, err = b.ConsumeUint32(); err != nil { return err } } if a.Flags&AttrPermissions != 0 { m, err := b.ConsumeUint32() if err != nil { return err } a.Permissions = FileMode(m) } if a.Flags&AttrACModTime != 0 { if a.ATime, err = b.ConsumeUint32(); err != nil { return err } if a.MTime, err = b.ConsumeUint32(); err != nil { return err } } if a.Flags&AttrExtended != 0 { count, err := b.ConsumeUint32() if err != nil { return err } a.ExtendedAttributes = make([]ExtendedAttribute, count) for i := range a.ExtendedAttributes { a.ExtendedAttributes[i].UnmarshalFrom(b) } } return nil } // UnmarshalBinary decodes the binary encoding of Attributes into e. func (a *Attributes) UnmarshalBinary(data []byte) error { return a.UnmarshalFrom(NewBuffer(data)) } // ExtendedAttribute defines the extended file attribute type defined in draft-ietf-secsh-filexfer-02 // // Defined in: https://tools.ietf.org/html/draft-ietf-secsh-filexfer-02#section-5 type ExtendedAttribute struct { Type string Data string } // Len returns the number of bytes e would marshal into. func (e *ExtendedAttribute) Len() int { return 4 + len(e.Type) + 4 + len(e.Data) } // MarshalInto marshals e onto the end of the given Buffer. func (e *ExtendedAttribute) MarshalInto(b *Buffer) { b.AppendString(e.Type) b.AppendString(e.Data) } // MarshalBinary returns e as the binary encoding of e. func (e *ExtendedAttribute) MarshalBinary() ([]byte, error) { buf := NewBuffer(make([]byte, 0, e.Len())) e.MarshalInto(buf) return buf.Bytes(), nil } // UnmarshalFrom unmarshals an ExtendedAattribute from the given Buffer into e. func (e *ExtendedAttribute) UnmarshalFrom(b *Buffer) (err error) { if e.Type, err = b.ConsumeString(); err != nil { return err } if e.Data, err = b.ConsumeString(); err != nil { return err } return nil } // UnmarshalBinary decodes the binary encoding of ExtendedAttribute into e. func (e *ExtendedAttribute) UnmarshalBinary(data []byte) error { return e.UnmarshalFrom(NewBuffer(data)) } // NameEntry implements the SSH_FXP_NAME repeated data type from draft-ietf-secsh-filexfer-02 // // This type is incompatible with versions 4 or higher. type NameEntry struct { Filename string Longname string Attrs Attributes } // Len returns the number of bytes e would marshal into. func (e *NameEntry) Len() int { return 4 + len(e.Filename) + 4 + len(e.Longname) + e.Attrs.Len() } // MarshalInto marshals e onto the end of the given Buffer. func (e *NameEntry) MarshalInto(b *Buffer) { b.AppendString(e.Filename) b.AppendString(e.Longname) e.Attrs.MarshalInto(b) } // MarshalBinary returns e as the binary encoding of e. func (e *NameEntry) MarshalBinary() ([]byte, error) { buf := NewBuffer(make([]byte, 0, e.Len())) e.MarshalInto(buf) return buf.Bytes(), nil } // UnmarshalFrom unmarshals an NameEntry from the given Buffer into e. // // NOTE: The values of fields not covered in the a.Flags are explicitly undefined. func (e *NameEntry) UnmarshalFrom(b *Buffer) (err error) { if e.Filename, err = b.ConsumeString(); err != nil { return err } if e.Longname, err = b.ConsumeString(); err != nil { return err } return e.Attrs.UnmarshalFrom(b) } // UnmarshalBinary decodes the binary encoding of NameEntry into e. func (e *NameEntry) UnmarshalBinary(data []byte) error { return e.UnmarshalFrom(NewBuffer(data)) }