257 lines
8.0 KiB
Go
257 lines
8.0 KiB
Go
// drive.go — MS-RDPEFS 设备 I/O 响应的编码器:目录枚举条目
|
||
// ([MS-FSCC] FILE_*_DIRECTORY_INFORMATION)、文件/卷信息结构与 UTF-16
|
||
// 辅助。时间统一从 JS 侧的毫秒时间戳换算 Windows FILETIME。
|
||
package rdpdr
|
||
|
||
import (
|
||
"encoding/binary"
|
||
"encoding/json"
|
||
"unicode/utf16"
|
||
)
|
||
|
||
// DirEntry 是桥接侧返回的单条目录/文件元数据。
|
||
type DirEntry struct {
|
||
Name string `json:"name"`
|
||
Dir bool `json:"dir"`
|
||
Size int64 `json:"size"`
|
||
Mtime int64 `json:"mtime"` // 毫秒时间戳(最后写入)
|
||
Created int64 `json:"created"` // 毫秒时间戳(缺省取 Mtime)
|
||
Accessed int64 `json:"accessed"` // 毫秒时间戳(缺省取 Mtime)
|
||
}
|
||
|
||
// FSCC 信息类(本实现支持的子集)。
|
||
const (
|
||
FileDirectoryInformation = 0x00000001
|
||
FileFullDirectoryInformation = 0x00000002
|
||
FileBothDirectoryInformation = 0x00000003
|
||
FileNamesInformation = 0x0000000C
|
||
|
||
FileFsVolumeInformation = 0x00000001
|
||
FileFsSizeInformation = 0x00000003
|
||
FileFsDeviceInformation = 0x00000004
|
||
FileFsAttributeInformation = 0x00000005
|
||
FileFsFullSizeInformation = 0x00000007
|
||
|
||
FileBasicInformation = 0x00000004
|
||
FileStandardInformation = 0x00000005
|
||
FileNetworkOpenInformation = 0x00000022
|
||
)
|
||
|
||
// filetime 把毫秒 Unix 时间戳换算为 Windows FILETIME(100ns,1601 纪元)。
|
||
func filetime(ms int64) uint64 {
|
||
const epochDelta = 116444736000000000 // 1601→1970 的 100ns 数
|
||
if ms < 0 {
|
||
ms = 0
|
||
}
|
||
return uint64(ms)*10000 + epochDelta
|
||
}
|
||
|
||
func utf16Bytes(s string) []byte {
|
||
u := utf16.Encode([]rune(s))
|
||
b := make([]byte, 2*len(u))
|
||
for i, v := range u {
|
||
binary.LittleEndian.PutUint16(b[2*i:], v)
|
||
}
|
||
return b
|
||
}
|
||
|
||
func utf16ToString(b []byte) string {
|
||
if len(b)%2 != 0 {
|
||
b = b[:len(b)-1]
|
||
}
|
||
u := make([]uint16, len(b)/2)
|
||
for i := range u {
|
||
u[i] = binary.LittleEndian.Uint16(b[2*i:])
|
||
}
|
||
runes := utf16.Decode(u)
|
||
// 去掉结尾 NUL
|
||
for n := len(runes) - 1; n >= 0; n-- {
|
||
if runes[n] == 0 {
|
||
runes = runes[:n]
|
||
} else {
|
||
break
|
||
}
|
||
}
|
||
return string(runes)
|
||
}
|
||
|
||
// decodeEntries 解析桥接 JSON 数组。
|
||
func decodeEntries(jsonStr string) ([]DirEntry, error) {
|
||
var entries []DirEntry
|
||
if err := json.Unmarshal([]byte(jsonStr), &entries); err != nil {
|
||
return nil, err
|
||
}
|
||
for i := range entries {
|
||
fillTimes(&entries[i])
|
||
}
|
||
return entries, nil
|
||
}
|
||
|
||
// decodeEntry 解析桥接 JSON 单对象。
|
||
func decodeEntry(jsonStr string) (DirEntry, error) {
|
||
var e DirEntry
|
||
if err := json.Unmarshal([]byte(jsonStr), &e); err != nil {
|
||
return e, err
|
||
}
|
||
fillTimes(&e)
|
||
return e, nil
|
||
}
|
||
|
||
func fillTimes(e *DirEntry) {
|
||
if e.Created == 0 {
|
||
e.Created = e.Mtime
|
||
}
|
||
if e.Accessed == 0 {
|
||
e.Accessed = e.Mtime
|
||
}
|
||
}
|
||
|
||
func (e *DirEntry) attributes() uint32 {
|
||
if e.Dir {
|
||
return FILE_ATTRIBUTE_DIRECTORY
|
||
}
|
||
if e.Size == 0 {
|
||
return FILE_ATTRIBUTE_NORMAL
|
||
}
|
||
return FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_ARCHIVE
|
||
}
|
||
|
||
// encodeDirEntries 把一批条目编码为 FSCC 目录信息结构链。
|
||
// 支持 FileDirectoryInformation(1)/FileBothDirectoryInformation(3)/
|
||
// FileNamesInformation(0xC);其它返回 nil(上层回 NOT_IMPLEMENTED)。
|
||
func encodeDirEntries(infoClass uint32, entries []DirEntry) []byte {
|
||
if len(entries) == 0 {
|
||
return []byte{}
|
||
}
|
||
var out []byte
|
||
for i := range entries {
|
||
e := &entries[i]
|
||
name := utf16Bytes(e.Name)
|
||
var raw []byte
|
||
switch infoClass {
|
||
case FileDirectoryInformation:
|
||
// NextEntryOffset(4) FileIndex(4) Creation(8) Access(8) Write(8)
|
||
// Change(8) EndOfFile(8) Allocation(8) Attributes(4) NameLen(4) = 64
|
||
raw = make([]byte, 64+len(name))
|
||
putTimes(raw, 8, e)
|
||
binary.LittleEndian.PutUint64(raw[40:], uint64(e.Size))
|
||
binary.LittleEndian.PutUint64(raw[48:], uint64(e.Size))
|
||
binary.LittleEndian.PutUint32(raw[56:], e.attributes())
|
||
binary.LittleEndian.PutUint32(raw[60:], uint32(len(name)))
|
||
copy(raw[64:], name)
|
||
case FileBothDirectoryInformation:
|
||
// 64 字节同上 + EaSize(4) ShortNameLen(1) ShortName(24) = 93
|
||
raw = make([]byte, 93+len(name))
|
||
putTimes(raw, 8, e)
|
||
binary.LittleEndian.PutUint64(raw[40:], uint64(e.Size))
|
||
binary.LittleEndian.PutUint64(raw[48:], uint64(e.Size))
|
||
binary.LittleEndian.PutUint32(raw[56:], e.attributes())
|
||
binary.LittleEndian.PutUint32(raw[60:], uint32(len(name)))
|
||
raw[68] = 0 // ShortNameLength
|
||
// ShortName[24] 全 0(无短名)
|
||
copy(raw[93:], name)
|
||
case FileNamesInformation:
|
||
// NextEntryOffset(4) FileIndex(4) FileNameLength(4) = 12
|
||
raw = make([]byte, 12+len(name))
|
||
binary.LittleEndian.PutUint32(raw[8:], uint32(len(name)))
|
||
copy(raw[12:], name)
|
||
default:
|
||
return nil
|
||
}
|
||
if i == len(entries)-1 {
|
||
// 末条:NextEntryOffset=0,无需对齐
|
||
binary.LittleEndian.PutUint32(raw[0:], 0)
|
||
out = append(out, raw...)
|
||
continue
|
||
}
|
||
// 非末条:长度 4 字节对齐,补零
|
||
next := (len(raw) + 3) &^ 3
|
||
buf := make([]byte, next)
|
||
copy(buf, raw)
|
||
binary.LittleEndian.PutUint32(buf[0:], uint32(next))
|
||
out = append(out, buf...)
|
||
}
|
||
return out
|
||
}
|
||
|
||
// putTimes 在 off 处写 Creation/Access/Write/Change 四个 FILETIME(32 字节)。
|
||
func putTimes(b []byte, off int, e *DirEntry) {
|
||
binary.LittleEndian.PutUint64(b[off:], filetime(e.Created))
|
||
binary.LittleEndian.PutUint64(b[off+8:], filetime(e.Accessed))
|
||
binary.LittleEndian.PutUint64(b[off+16:], filetime(e.Mtime))
|
||
binary.LittleEndian.PutUint64(b[off+24:], filetime(e.Mtime))
|
||
}
|
||
|
||
// encodeFileInfo 编码 QUERY_INFORMATION 响应体(不含 Length 前缀)。
|
||
func encodeFileInfo(infoClass uint32, e DirEntry) []byte {
|
||
switch infoClass {
|
||
case FileBasicInformation:
|
||
// Creation(8) Access(8) Write(8) Change(8) Attributes(4) Reserved(4) = 40
|
||
b := make([]byte, 40)
|
||
putTimes(b, 0, &e)
|
||
binary.LittleEndian.PutUint32(b[32:], e.attributes())
|
||
return b
|
||
case FileStandardInformation:
|
||
// AllocationSize(8) EndOfFile(8) NumberOfLinks(4) DeletePending(1) Directory(1) = 22
|
||
b := make([]byte, 22)
|
||
binary.LittleEndian.PutUint64(b[0:], uint64(e.Size))
|
||
binary.LittleEndian.PutUint64(b[8:], uint64(e.Size))
|
||
binary.LittleEndian.PutUint32(b[16:], 1)
|
||
b[20] = 0
|
||
if e.Dir {
|
||
b[21] = 1
|
||
}
|
||
return b
|
||
case FileNetworkOpenInformation:
|
||
// Creation(8) Access(8) Write(8) Change(8) Allocation(8) EndOfFile(8) Attributes(4) = 56
|
||
b := make([]byte, 56)
|
||
putTimes(b, 0, &e)
|
||
binary.LittleEndian.PutUint64(b[32:], uint64(e.Size))
|
||
binary.LittleEndian.PutUint64(b[40:], uint64(e.Size))
|
||
binary.LittleEndian.PutUint32(b[48:], e.attributes())
|
||
return b
|
||
default:
|
||
return nil
|
||
}
|
||
}
|
||
|
||
// encodeVolumeInfo 编码 QUERY_VOLUME_INFORMATION 响应体(不含 Length 前缀)。
|
||
func encodeVolumeInfo(infoClass uint32, label string) []byte {
|
||
switch infoClass {
|
||
case FileFsVolumeInformation:
|
||
// VolumeCreationTime(8) SerialNumber(4) LabelLength(4) SupportsObjects(1) Label
|
||
vol := utf16Bytes(label)
|
||
b := make([]byte, 17+len(vol))
|
||
binary.LittleEndian.PutUint32(b[8:], 0x1ABCF2D8) // 任意固定序列号
|
||
binary.LittleEndian.PutUint32(b[12:], uint32(len(vol)))
|
||
copy(b[17:], vol)
|
||
return b
|
||
case FileFsSizeInformation, FileFsFullSizeInformation:
|
||
// TotalAllocationUnits(8) Available(8) SectorsPerUnit(4) BytesPerSector(4) = 24
|
||
// (Win10 服务器挂载设备时常探测 FullSize——缺失会致设备"不支持")
|
||
b := make([]byte, 24)
|
||
binary.LittleEndian.PutUint64(b[0:], 0x00100000)
|
||
binary.LittleEndian.PutUint64(b[8:], 0x00080000)
|
||
binary.LittleEndian.PutUint32(b[16:], 8)
|
||
binary.LittleEndian.PutUint32(b[20:], 512)
|
||
return b
|
||
case FileFsDeviceInformation:
|
||
// DeviceType(4)=FILE_DEVICE_DISK Characteristics(4) = 8
|
||
b := make([]byte, 8)
|
||
binary.LittleEndian.PutUint32(b[0:], 7)
|
||
return b
|
||
case FileFsAttributeInformation:
|
||
// Attributes(4) MaxComponentLen(4) NameLength(4) Name("FAT32",规避
|
||
// 服务端按 NTFS 语义发起的 ACL/重解析点等操作——mstsc/FreeRDP 同款选择)
|
||
fs := utf16Bytes("FAT32")
|
||
b := make([]byte, 12+len(fs))
|
||
binary.LittleEndian.PutUint32(b[0:], 0x00000007) // CASE_SENSITIVE_SEARCH|CASE_PRESERVED_NAMES|UNICODE_ON_DISK
|
||
binary.LittleEndian.PutUint32(b[4:], 255)
|
||
binary.LittleEndian.PutUint32(b[8:], uint32(len(fs)))
|
||
copy(b[12:], fs)
|
||
return b
|
||
default:
|
||
return nil
|
||
}
|
||
}
|