package cliprdr import ( "bytes" "encoding/binary" "fmt" "log" "strings" "sync" "unicode/utf16" ) // Sender 负责把 CLIPRDR PDU 通过 MCS SendDataRequest 发往指定 channel ID。 // 由协议栈注入(通常封装 buildMCSSendDataRequest + transport.WriteX224Data + writeMu)。// Logan.Li type Sender interface { SendChannelPDU(channelID uint16, data []byte) error } // Handler 实现 RDP 剪贴板虚拟通道(CF_UNICODETEXT / CF_TEXT 文本格式)。 // // - onRemote: 远端剪贴板文本到达时回调(协议栈应转成 WS type=clipboard 帧推给浏览器) // - getLocal: 远端请求本地剪贴板时回调取值(协议栈从 inputLoop 维护的本地剪贴板缓存读取) // // 两个回调都是线程安全的调用点:Handler 内部用 mu 保护自身状态, // 调用方负责 Sender 的并发写(writeMu)。 type Handler struct { sender Sender channelID uint16 mu sync.Mutex useLongFormatNames bool serverCapsReceived bool monitorReady bool localText string // 本地剪贴板文本(由 SetLocalText 更新,供远端请求时回填) suppressNextChange bool // 抑制 echo 回环:远端→本地 触发的本地变更不再回推远端 onRemote func(text string) // 远端文本到达回调(可能为 nil) initialFormatListSent bool // 初始化阶段只允许发送一次空 FormatList,匹配 FreeRDP/IronRDP 时序。// Logan.Li } // NewHandler 创建剪贴板处理器。 // - onRemote: 远端剪贴板内容到达时调用(转成 WS clipboard 帧推送浏览器) // - getLocal: 已废弃,本地文本请用 SetLocalText 主动设置(浏览器同步剪贴板时写入) func NewHandler(onRemote func(text string)) *Handler { return &Handler{onRemote: onRemote} } // Attach 绑定发送通道与会话分配的 channel ID(在 MCS ChannelJoin 成功后调用)。 // 附加后主动发 CB_CLIP_CAPS 声明客户端能力(必须用 Long Format Names), // 否则 Windows 不知道格式协商方式,会忽略后续 FormatList。 // Logan.Li func (h *Handler) Attach(sender Sender, channelID uint16) { h.mu.Lock() h.sender = sender h.channelID = channelID h.useLongFormatNames = true // 客户端强制 Long Format Names,与发送的 caps 一致 h.mu.Unlock() log.Printf("[cliprdr] attached channelID=%d", channelID) } // Init 在会话激活后主动发起剪贴板握手。 // MS-RDPECLIP 规定服务端先发 CB_CLIP_CAPS + CB_MONITOR_READY,但部分 Windows 版本 // (尤其开启了剪贴板重定向策略的)会等客户端先声明能力。主动发 caps + formatList // 可兼容两种情况,不会产生冲突(服务端收到多余的 caps 会忽略)。// Logan.Li func (h *Handler) Init() { h.mu.Lock() if h.sender == nil { h.mu.Unlock() return } monitorReady := h.monitorReady needInitial := !h.initialFormatListSent if needInitial { h.initialFormatListSent = true } h.mu.Unlock() if monitorReady { log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr init: server MonitorReady already handled")) return } // 部分 Windows 会先等客户端声明能力;延迟主动初始化兼顾标准时序和真实服务器兼容性。// Logan.Li h.sendClipCaps() if needInitial { h.sendEmptyFormatList() } log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr init: proactive caps and initial FormatList sent")) } // SetLocalText 更新本地剪贴板文本(浏览器把本地剪贴板同步过来时调用)。 // 设置后会向远端发送 FormatList 通知远端"本地剪贴板有新内容", // 除非 suppress 为 true(用于抑制远端→本地 echo 回环)。 func (h *Handler) SetLocalText(text string) { h.mu.Lock() h.localText = text suppress := h.suppressNextChange h.suppressNextChange = false sender := h.sender h.mu.Unlock() if suppress { log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: local text set with suppress (echo from remote), skip FormatList")) return } if sender != nil { h.sendFormatList() log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: local text changed, sent FormatList", "len", len(text))) } } // ChannelID 返回绑定的通道 ID(未 Attach 返回 0)。 func (h *Handler) ChannelID() uint16 { h.mu.Lock() defer h.mu.Unlock() return h.channelID } // RequestRemoteText 在用户触发远端 Ctrl+C/Ctrl+X 后主动请求文本内容, // 兼容部分 Windows 不主动发送 CB_FORMAT_LIST 的场景。// Logan.Li func (h *Handler) RequestRemoteText() { h.mu.Lock() ready := h.sender != nil h.mu.Unlock() if !ready { return } h.sendFormatDataRequest(cfUnicodeText) } // Process 处理从 MCS SendDataIndication 收到的 cliprdr PDU(已去掉 MCS/SEC 头的原始通道数据)。 // 由协议栈 readLoop 在识别到 cliprdr channel ID 后调用。 func (h *Handler) Process(data []byte) { if len(data) < 8 { return } msgType := binary.LittleEndian.Uint16(data[0:2]) msgFlags := binary.LittleEndian.Uint16(data[2:4]) dataLen := binary.LittleEndian.Uint32(data[4:8]) if int(dataLen) > len(data)-8 { log.Printf("[cliprdr] Warn: %s", fmt.Sprintln("cliprdr: PDU dataLen exceeds payload", "dataLen", dataLen, "have", len(data)-8)) return } body := data[8 : 8+dataLen] log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr recv", "msgType", msgType, "msgFlags", msgFlags, "dataLen", dataLen)) switch msgType { case msgClipCaps: h.processClipCaps(body) case msgMonitorReady: h.processMonitorReady() case msgFormatList: h.processFormatList(body) case msgFormatListResponse: // 远端确认收到本地的 FormatList,无需动作 if msgFlags&flagResponseOK == 0 { log.Printf("[cliprdr] Warn: %s", fmt.Sprintln("cliprdr: FormatListResponse FAIL")) } case msgFormatDataRequest: h.processFormatDataRequest(body) case msgFormatDataResponse: h.processFormatDataResponse(body, msgFlags) case msgLockClipdata, msgUnlockClipdata: // 文本模式不处理锁 default: log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: unhandled msgType", "msgType", msgType)) } } // --- CB_CLIP_CAPS(2.2.2.1)------------------------------------------------- func (h *Handler) processClipCaps(body []byte) { if len(body) < 4 { return } cCapSets := binary.LittleEndian.Uint16(body[0:2]) offset := 4 // cCapabilitySets(2) + pad1(2) for i := 0; i < int(cCapSets); i++ { if offset+4 > len(body) { break } capType := binary.LittleEndian.Uint16(body[offset:]) capLen := binary.LittleEndian.Uint16(body[offset+2:]) if capType == capsTypeGeneral && capLen >= 12 && offset+12 <= len(body) { generalFlags := binary.LittleEndian.Uint32(body[offset+8:]) h.mu.Lock() h.useLongFormatNames = generalFlags&flagUseLongFormatNames != 0 useLong := h.useLongFormatNames h.mu.Unlock() log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: server caps", "generalFlags", generalFlags, "longNames", useLong)) } offset += int(capLen) } h.mu.Lock() h.serverCapsReceived = true localReady := h.localText != "" ready := h.monitorReady h.mu.Unlock() // 非标准顺序:MonitorReady 先到时,在拿到 caps 后补发 FormatList if ready && localReady { h.sendFormatList() } } // --- CB_MONITOR_READY(2.2.2.2)-------------------------------------------- func (h *Handler) processMonitorReady() { log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: server Monitor Ready")) h.mu.Lock() h.monitorReady = true capsReady := h.serverCapsReceived localReady := h.localText != "" needInitial := !h.initialFormatListSent if needInitial { h.initialFormatListSent = true } h.mu.Unlock() // MS-RDPECLIP §1.3.2.1: server 先发 CB_CLIP_CAPS 再发 CB_MONITOR_READY。 // 拿到 caps 才知道是否用 Long Format Names,故 FormatList 等 caps 就绪后再发。 h.sendClipCaps() if needInitial && !localReady { h.sendEmptyFormatList() log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: sent initial empty FormatList")) } if capsReady && localReady { h.sendFormatList() } } // --- Format List(2.2.3)--------------------------------------------------- func (h *Handler) sendFormatList() { h.mu.Lock() useLong := h.useLongFormatNames h.initialFormatListSent = true h.mu.Unlock() body := &bytes.Buffer{} if useLong { // Long Format Name: formatId(4) + null-terminated UTF-16LE name binary.Write(body, binary.LittleEndian, uint32(cfUnicodeText)) binary.Write(body, binary.LittleEndian, uint16(0)) // 空名 = 标准格式 binary.Write(body, binary.LittleEndian, uint32(cfText)) binary.Write(body, binary.LittleEndian, uint16(0)) } else { // Short Format Name: formatId(4) + name[32] binary.Write(body, binary.LittleEndian, uint32(cfUnicodeText)) body.Write(make([]byte, 32)) binary.Write(body, binary.LittleEndian, uint32(cfText)) body.Write(make([]byte, 32)) } h.sendPDU(msgFormatList, 0, body.Bytes()) } func (h *Handler) sendEmptyFormatList() { // 初始化空 FormatList 是 FreeRDP 允许的首包,用来完成剪贴板客户端初始化。// Logan.Li h.sendPDU(msgFormatList, 0, nil) } func (h *Handler) processFormatList(body []byte) { formats := h.parseFormatList(body) log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: server FormatList", "formats", formats)) // 始终回复 OK h.sendPDU(msgFormatListResponse, flagResponseOK, nil) // 优先请求 Unicode 文本,其次 ANSI for _, f := range formats { if f == cfUnicodeText { h.sendFormatDataRequest(cfUnicodeText) return } } for _, f := range formats { if f == cfText { h.sendFormatDataRequest(cfText) return } } } func (h *Handler) parseFormatList(body []byte) []uint32 { h.mu.Lock() useLong := h.useLongFormatNames h.mu.Unlock() var formats []uint32 if useLong { // Long: formatId(4) + null-term UTF-16LE name offset := 0 for offset+4 <= len(body) { formats = append(formats, binary.LittleEndian.Uint32(body[offset:])) offset += 4 // 跳过 name(null-terminated UTF-16LE) for offset+1 < len(body) { if body[offset] == 0 && body[offset+1] == 0 { offset += 2 break } offset += 2 } } } else { // Short: formatId(4) + name[32] = 36 字节/项 for offset := 0; offset+36 <= len(body); offset += 36 { formats = append(formats, binary.LittleEndian.Uint32(body[offset:])) } } return formats } // --- Format Data Request / Response(2.2.5)------------------------------- func (h *Handler) sendFormatDataRequest(formatID uint32) { b := make([]byte, 4) binary.LittleEndian.PutUint32(b, formatID) h.sendPDU(msgFormatDataRequest, 0, b) log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: sent FormatDataRequest", "formatID", formatID)) } func (h *Handler) processFormatDataRequest(body []byte) { if len(body) < 4 { h.sendPDU(msgFormatDataResponse, flagResponseFail, nil) return } requestedFormat := binary.LittleEndian.Uint32(body[0:4]) h.mu.Lock() text := h.localText h.mu.Unlock() log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: server requests format", "formatID", requestedFormat, "localLen", len(text))) switch requestedFormat { case cfUnicodeText: encoded := encodeUTF16LE(text + "\x00") h.sendPDU(msgFormatDataResponse, flagResponseOK, encoded) case cfText: h.sendPDU(msgFormatDataResponse, flagResponseOK, []byte(text+"\x00")) default: h.sendPDU(msgFormatDataResponse, flagResponseFail, nil) } } func (h *Handler) processFormatDataResponse(body []byte, msgFlags uint16) { if msgFlags&flagResponseOK == 0 { log.Printf("[cliprdr] Warn: %s", fmt.Sprintln("cliprdr: FormatDataResponse FAIL")) return } // 尝试按 UTF-16LE 解码(CF_UNICODETEXT) text := decodeUTF16LE(body) text = strings.TrimRight(text, "\x00") if text == "" { return } log.Printf("[cliprdr] Debug: %s", fmt.Sprintln("cliprdr: remote clipboard text", "len", len(text))) // 标记抑制下一次本地变更(避免 echo 回环:远端→本地触发的本地变更不再推回远端) h.mu.Lock() h.suppressNextChange = true cb := h.onRemote h.mu.Unlock() if cb != nil { cb(text) } } // --- Send helpers -------------------------------------------------------- func (h *Handler) sendClipCaps() { // General capability set: type(2) + length(2) + version(4) + flags(4) = 12 cap := &bytes.Buffer{} binary.Write(cap, binary.LittleEndian, uint16(capsTypeGeneral)) binary.Write(cap, binary.LittleEndian, uint16(12)) binary.Write(cap, binary.LittleEndian, uint32(capsVersion2)) binary.Write(cap, binary.LittleEndian, uint32(flagUseLongFormatNames)) body := &bytes.Buffer{} binary.Write(body, binary.LittleEndian, uint16(1)) // cCapabilitySets binary.Write(body, binary.LittleEndian, uint16(0)) // pad1 body.Write(cap.Bytes()) h.sendPDU(msgClipCaps, 0, body.Bytes()) } func (h *Handler) sendPDU(msgType, msgFlags uint16, body []byte) { h.mu.Lock() sender := h.sender chID := h.channelID h.mu.Unlock() if sender == nil { return } buf := &bytes.Buffer{} binary.Write(buf, binary.LittleEndian, msgType) binary.Write(buf, binary.LittleEndian, msgFlags) binary.Write(buf, binary.LittleEndian, uint32(len(body))) buf.Write(body) if err := sender.SendChannelPDU(chID, buf.Bytes()); err != nil { log.Printf("[cliprdr] Warn: %s", fmt.Sprintln("cliprdr: send PDU failed", "msgType", msgType, "err", err)) } } // --- UTF-16LE helpers ---------------------------------------------------- func decodeUTF16LE(b []byte) string { if len(b) < 2 { return "" } if len(b)%2 != 0 { b = b[:len(b)-1] } u16 := make([]uint16, len(b)/2) for i := range u16 { u16[i] = binary.LittleEndian.Uint16(b[i*2:]) } return string(utf16.Decode(u16)) } func encodeUTF16LE(s string) []byte { u16 := utf16.Encode([]rune(s)) b := make([]byte, len(u16)*2) for i, v := range u16 { binary.LittleEndian.PutUint16(b[i*2:], v) } return b }