This repository has been archived by the owner on Jul 17, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
GUACAMOLE-44: Implement intercepting of input streams.
- Loading branch information
1 parent
f391f00
commit 131785a
Showing
3 changed files
with
298 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
198 changes: 198 additions & 0 deletions
198
guacamole/src/main/java/org/apache/guacamole/tunnel/InputStreamInterceptingFilter.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,198 @@ | ||
/* | ||
* 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 org.apache.guacamole.tunnel; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import javax.xml.bind.DatatypeConverter; | ||
import org.apache.guacamole.GuacamoleException; | ||
import org.apache.guacamole.net.GuacamoleTunnel; | ||
import org.apache.guacamole.protocol.GuacamoleInstruction; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
/** | ||
* Filter which selectively intercepts "ack" instructions, automatically reading | ||
* from or closing the stream given with interceptStream(). The required "blob" | ||
* and "end" instructions denoting the content and boundary of the stream are | ||
* sent automatically. | ||
*/ | ||
public class InputStreamInterceptingFilter | ||
extends StreamInterceptingFilter<InputStream> { | ||
|
||
/** | ||
* Logger for this class. | ||
*/ | ||
private static final Logger logger = | ||
LoggerFactory.getLogger(InputStreamInterceptingFilter.class); | ||
|
||
/** | ||
* Creates a new InputStreamInterceptingFilter which selectively intercepts | ||
* "ack" instructions. The required "blob" and "end" instructions will | ||
* automatically be sent over the given tunnel based on the content of | ||
* provided InputStreams. | ||
* | ||
* @param tunnel | ||
* The GuacamoleTunnel over which any required "blob" and "end" | ||
* instructions should be sent. | ||
*/ | ||
public InputStreamInterceptingFilter(GuacamoleTunnel tunnel) { | ||
super(tunnel); | ||
} | ||
|
||
/** | ||
* Injects a "blob" instruction into the outbound Guacamole protocol | ||
* stream, as if sent by the connected client. "blob" instructions are used | ||
* to send chunks of data along a stream. | ||
* | ||
* @param index | ||
* The index of the stream that this "blob" instruction relates to. | ||
* | ||
* @param blob | ||
* The chunk of data to send within the "blob" instruction. | ||
*/ | ||
private void sendBlob(String index, byte[] blob) { | ||
|
||
// Send "blob" containing provided data | ||
sendInstruction(new GuacamoleInstruction("blob", index, | ||
DatatypeConverter.printBase64Binary(blob))); | ||
|
||
} | ||
|
||
/** | ||
* Injects an "end" instruction into the outbound Guacamole protocol | ||
* stream, as if sent by the connected client. "end" instructions are used | ||
* to signal the end of a stream. | ||
* | ||
* @param index | ||
* The index of the stream that this "end" instruction relates to. | ||
*/ | ||
private void sendEnd(String index) { | ||
sendInstruction(new GuacamoleInstruction("end", index)); | ||
} | ||
|
||
/** | ||
* Reads the next chunk of data from the InputStream associated with an | ||
* intercepted stream, sending that data as a "blob" instruction over the | ||
* GuacamoleTunnel associated with this filter. If the end of the | ||
* InputStream is reached, an "end" instruction will automatically be sent. | ||
* | ||
* @param stream | ||
* The stream from which the next chunk of data should be read. | ||
*/ | ||
private void readNextBlob(InterceptedStream<InputStream> stream) { | ||
|
||
// Read blob from stream if it exists | ||
try { | ||
|
||
// Read raw data from input stream | ||
byte[] blob = new byte[6048]; | ||
int length = stream.getStream().read(blob); | ||
|
||
// End stream if no more data | ||
if (length == -1) { | ||
|
||
// Close stream, send end if the stream is still valid | ||
if (closeInterceptedStream(stream)) | ||
sendEnd(stream.getIndex()); | ||
|
||
return; | ||
|
||
} | ||
|
||
// Inject corresponding "blob" instruction | ||
sendBlob(stream.getIndex(), Arrays.copyOf(blob, length)); | ||
|
||
} | ||
|
||
// Terminate stream if it cannot be read | ||
catch (IOException e) { | ||
|
||
logger.debug("Unable to read data of intercepted input stream.", e); | ||
|
||
// Close stream, send end if the stream is still valid | ||
if (closeInterceptedStream(stream)) | ||
sendEnd(stream.getIndex()); | ||
|
||
} | ||
|
||
} | ||
|
||
/** | ||
* Handles a single "ack" instruction, sending yet more blobs or closing the | ||
* stream depending on whether the "ack" indicates success or failure. If no | ||
* InputStream is associated with the stream index within the "ack" | ||
* instruction, the instruction is ignored. | ||
* | ||
* @param instruction | ||
* The "ack" instruction being handled. | ||
*/ | ||
private void handleAck(GuacamoleInstruction instruction) { | ||
|
||
// Verify all required arguments are present | ||
List<String> args = instruction.getArgs(); | ||
if (args.size() < 3) | ||
return; | ||
|
||
// Pull associated stream | ||
String index = args.get(0); | ||
InterceptedStream<InputStream> stream = getInterceptedStream(index); | ||
if (stream == null) | ||
return; | ||
|
||
// Pull status code | ||
String status = args.get(2); | ||
|
||
// Terminate stream if an error is encountered | ||
if (!status.equals("0")) { | ||
closeInterceptedStream(stream); | ||
return; | ||
} | ||
|
||
// Send next blob | ||
readNextBlob(stream); | ||
|
||
} | ||
|
||
@Override | ||
public GuacamoleInstruction filter(GuacamoleInstruction instruction) | ||
throws GuacamoleException { | ||
|
||
// Intercept "ack" instructions for in-progress streams | ||
if (instruction.getOpcode().equals("ack")) | ||
handleAck(instruction); | ||
|
||
// Pass instruction through untouched | ||
return instruction; | ||
|
||
} | ||
|
||
@Override | ||
protected void handleInterceptedStream(InterceptedStream<InputStream> stream) { | ||
|
||
// Read the first blob. Note that future blobs will be read in response | ||
// to received "ack" instructions. | ||
readNextBlob(stream); | ||
|
||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters