blob: 776f4516b5214aa4c34fc6e5121499764818900d [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package rdpclient.clip;
import java.util.HashMap;
import java.util.Map;
import rdpclient.rdp.RdpConstants;
import streamer.BaseElement;
import streamer.ByteBuffer;
import streamer.Element;
import streamer.Link;
import streamer.Pipeline;
import streamer.PipelineImpl;
import streamer.debug.MockSink;
import streamer.debug.MockSource;
public class ServerFormatListPDU extends BaseElement {
protected ClipboardState state;
public ServerFormatListPDU(String id, ClipboardState state) {
super(id);
this.state = state;
}
@Override
public void handleData(ByteBuffer buf, Link link) {
if (verbose)
System.out.println("[" + this + "] INFO: Data received: " + buf + ".");
parseFormatNames(buf);
buf.unref();
// Automatically send request for text-based data to insert it into local
// clipboard
ClipboardDataFormat textFormat = ClipboardDataFormat.findBestTextFormat(state.serverClipboardDataFormats);
if (textFormat != null) {
// Send response: OK
sendFormatListParseResponse(true);
// Request data
sendFormatDataRequest(textFormat);
} else {
// Send response: FAIL, we are not interested in this data
sendFormatListParseResponse(false);
}
}
/**
* The Format Data Request PDU is sent by the recipient of the Format List
* PDU. It is used to request the data for one of the formats that was listed
* in the Format List PDU.
*/
protected void sendFormatDataRequest(ClipboardDataFormat textFormat) {
if (verbose)
System.out.println("[" + this + "] INFO: Sending request for data in following format: " + textFormat + ".");
// Store data format to parse server response later
state.serverRequestedFormat = textFormat;
ByteBuffer buf = new ByteBuffer(12, true);
// Type
buf.writeShortLE(ServerClipRdrChannelRouter.CB_FORMAT_DATA_REQUEST);
// Message flags
buf.writeShortLE(0);
// Length
buf.writeIntLE(4);
// ID of chosen format
buf.writeIntLE(textFormat.id);
buf.trimAtCursor();
pushDataToPad(STDOUT, buf);
}
/**
* The Format List Response PDU is sent as a reply to the Format List PDU. It
* is used to indicate whether processing of the Format List PDU was
* successful.
*
* @param b
*/
protected void sendFormatListParseResponse(boolean ok) {
ByteBuffer buf = new ByteBuffer(8, true);
// Type
buf.writeShortLE(ServerClipRdrChannelRouter.CB_FORMAT_LIST_RESPONSE);
// Message flags
buf.writeShortLE((ok) ? ServerClipRdrChannelRouter.CB_RESPONSE_OK : ServerClipRdrChannelRouter.CB_RESPONSE_FAIL);
// Length
buf.writeIntLE(0);
buf.trimAtCursor();
pushDataToPad(STDOUT, buf);
}
protected void parseFormatNames(ByteBuffer buf) {
// Set will not be modified after creation, so there is no need to make it
// synchronous.
Map<Object, ClipboardDataFormat> formats = new HashMap<Object, ClipboardDataFormat>();
while (buf.cursor < buf.length) {
int id = buf.readSignedIntLE();
String name;
if (state.serverUseLongFormatNames) {
// Long format names in Unicode
name = buf.readVariableWideString(RdpConstants.CHARSET_16);
} else {
Boolean asciiNames = (Boolean)buf.getMetadata(ServerClipRdrChannelRouter.ASCII_NAMES);
if (asciiNames != null && asciiNames) {
// Short format names in ASCII
name = buf.readString(32, RdpConstants.CHARSET_8);
} else {
// Short format names in Unicode
name = buf.readString(32, RdpConstants.CHARSET_16);
}
}
// Store format in map by both ID and name (if name is not empty)
formats.put(id, new ClipboardDataFormat(id, name));
if (name.length() > 0)
formats.put(name, new ClipboardDataFormat(id, name));
}
if (verbose)
System.out.println("Server supports following formats for clipboard data: " + formats.values().toString() + ".");
state.serverClipboardDataFormats = formats;
}
/**
* Example.
*/
public static void main(String[] args) {
// System.setProperty("streamer.Link.debug", "true");
System.setProperty("streamer.Element.debug", "true");
// System.setProperty("streamer.Pipeline.debug", "true");
/* @formatter:off */
byte[] packet = new byte[] {
0x02, 0x00, // CLIPRDR_HEADER::msgType = CB_FORMAT_LIST (2)
0x00, 0x00, // CLIPRDR_HEADER::msgFlags = 0
(byte) 0xe0, 0x00, 0x00, 0x00, // CLIPRDR_HEADER::dataLen = 0xe0 = 224 bytes
(byte) 0x8a, (byte) 0xc0, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 0xc08a = 49290
0x52, 0x00, 0x69, 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x20, 0x00, 0x46, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatName = "Rich Text Format"
0x45, (byte) 0xc1, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 0xc145 = 49477
0x52, 0x00, 0x69, 0x00, 0x63, 0x00, 0x68, 0x00, 0x20, 0x00, 0x54, 0x00, 0x65, 0x00, 0x78, 0x00,
0x74, 0x00, 0x20, 0x00, 0x46, 0x00, 0x6f, 0x00, 0x72, 0x00, 0x6d, 0x00, 0x61, 0x00, 0x74, 0x00,
0x20, 0x00, 0x57, 0x00, 0x69, 0x00, 0x74, 0x00, 0x68, 0x00, 0x6f, 0x00, 0x75, 0x00, 0x74, 0x00,
0x20, 0x00, 0x4f, 0x00, 0x62, 0x00, 0x6a, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x73, 0x00,
0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatName = "Rich Text Format Without Objects"
0x43, (byte) 0xc1, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 0xc143 = 49475
0x52, 0x00, 0x54, 0x00, 0x46, 0x00, 0x20, 0x00, 0x41, 0x00, 0x73, 0x00, 0x20, 0x00, 0x54, 0x00,
0x65, 0x00, 0x78, 0x00, 0x74, 0x00, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatName = "RTF As Text"
0x01, 0x00, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 1
0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatName = ""
0x0d, 0x00, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 0x0d = 13
0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatName = ""
0x04, (byte) 0xc0, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 0xc004 = 49156
0x4e, 0x00, 0x61, 0x00, 0x74, 0x00, 0x69, 0x00, 0x76, 0x00, 0x65, 0x00, 0x00, 0x00, // "Native"
0x0e, (byte) 0xc0, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 0xc00e = 49166
0x4f, 0x00, 0x62, 0x00, 0x6a, 0x00, 0x65, 0x00, 0x63, 0x00, 0x74, 0x00, 0x20, 0x00, 0x44, 0x00,
0x65, 0x00, 0x73, 0x00, 0x63, 0x00, 0x72, 0x00, 0x69, 0x00, 0x70, 0x00, 0x74, 0x00, 0x6f, 0x00,
0x72, 0x00, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatName = "Object Descriptor"
0x03, 0x00, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 3
0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatName = ""
0x10, 0x00, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 16
0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatName = ""
0x07, 0x00, 0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatId = 7
0x00, 0x00, // CLIPRDR_LONG_FORMAT_NAME::formatName = ""
};
/* @formatter:on */
MockSource source = new MockSource("source", ByteBuffer.convertByteArraysToByteBuffers(packet));
Element router = new ServerClipRdrChannelRouter("router");
ClipboardState state = new ClipboardState();
state.serverUseLongFormatNames = true;
Element format_list = new ServerFormatListPDU("format_list", state);
Element sink = new MockSink("sink", ByteBuffer.convertByteArraysToByteBuffers(new byte[] {
// Format List Response PDU
0x03, 0x00, // CLIPRDR_HEADER::msgType = CB_FORMAT_LIST_RESPONSE (3)
0x01, 0x00, // CLIPRDR_HEADER::msgFlags = 0x0001 = CB_RESPONSE_OK
0x00, 0x00, 0x00, 0x00, // CLIPRDR_HEADER::dataLen = 0 bytes
}, new byte[] {
// Format Data Request PDU
0x04, 0x00, // CLIPRDR_HEADER::msgType = CB_FORMAT_DATA_REQUEST (4)
0x00, 0x00, // CLIPRDR_HEADER::msgFlags = 0
0x04, 0x00, 0x00, 0x00, // CLIPRDR_HEADER::dataLen = 4 bytes
0x0d, 0x00, 0x00, 0x00, // CLIPRDR_FORMAT_DATA_REQUEST::requestedFormatId
// = 0x0d
}));
Pipeline pipeline = new PipelineImpl("test");
pipeline.add(source, router, format_list, sink);
pipeline.link("source", "router >format_list", "format_list", "sink");
pipeline.runMainLoop("source", STDOUT, false, false);
// Check state
if (!(state.serverClipboardDataFormats.containsKey(49475) && state.serverClipboardDataFormats.containsKey("Rich Text Format")))
throw new RuntimeException("Server format list packet parsed incorrectly.");
}
}