Files
rdplib/plugin/rdpdr/drive.go
T

257 lines
8.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 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
}
}