init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib
This commit is contained in:
@@ -0,0 +1,256 @@
|
||||
// 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user