init: 自 zomaintain/backend/rdplib 平移独立成库; module path 从上游 github.com/nakagami/grdp 改为 git.zeroonesoft.cn/golib/rdplib

This commit is contained in:
w11
2026-09-19 18:55:43 +08:00
commit 1096fbce9d
94 changed files with 36790 additions and 0 deletions
+256
View File
@@ -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
}
}