// 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 } }