/**
* This file is part of the VNCProxy program.
* <p>
* VNCPRoxy Summary :
* In just one clic (no setup) this Java Applet based solution
* allows you to run VNC Server / VNC Viewer
* through an HTTP AES encrypted tunnel.
* As it is full HTTP, there is no proxy or firewall setup needed.
* <p>
* Copyright (C) 2009 - Remi Serrano - http://www.vncproxy.com
* <p>
* VNCProxy is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* VNCProxy is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License
* along with VNCProxy. If not, see <http://www.gnu.org/licenses/>.
*
*/
package com.vncproxy.hub;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import com.vncproxy.applet.VNCPSession;
/**
* This class implements the VNCPHubListener Servlet. The VNCPHubListener is in
* charge of the data transfer from a VNCProxy Applet to another.
*
* @author Remi Serrano
*
*/
public class VNCPHubListener extends HttpServlet {
/**
* This constant variable represents the NUMBER_24
*/
private static final int NUMBER_24 = 24;
/**
* This constant variable represents the NUMBER_16
*/
private static final int NUMBER_16 = 16;
/**
* This constant variable represents the NUMBER_8
*/
private static final int NUMBER_8 = 8;
/**
* This constant variable represents the NUMBER_3
*/
private static final int NUMBER_3 = 3;
/**
* This constant variable represents the NUMBER_0XFF
*/
private static final int NUMBER_0XFF = 0xff;
/**
* This constant variable is needed to implements java.io.Serializable
*/
private static final long serialVersionUID = 1L;
/**
* This constant variable represents the time out value in milliseconds
*/
private static final long TIMEOUT = 60 * 60 * 1000;
/**
* This constant variable represents the byte size of the "sid" integer
*/
private static final int SID_BYTE_SIZE = 4;
/**
* This constant variable represents the Offset used in the data length
* computation
*/
private static final int CONTENT_LEN_OFFSET = 1;
/**
* This constant variable represents the time to wait for while waiting for
* the buffer to be ready, in millisecond
*/
private static final long SLEEP_TIME = 2;
/**
* This method overrides the Servlet doPost method. It is in charge of the
* data transfer from a VNCProxy Applet to another.
*
* @param request
* The HTTP request
* @param response
* The HTTP response
* @throws IOException
* Any IO Exception
* @throws ServletException
* Any Servlet Exception
*
*/
@Override
public final void doPost(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
ServletContext context = getServletConfig().getServletContext();
DataInputStream dis = new DataInputStream(request.getInputStream());
DataOutputStream dos = new DataOutputStream(response.getOutputStream());
try {
// READ From Applet
byte[] sessionId = new byte[SID_BYTE_SIZE];
byte[] type = new byte[1];
byte[] data = new byte[request.getContentLength() - (SID_BYTE_SIZE + CONTENT_LEN_OFFSET)];
dis.readFully(sessionId);
dis.readFully(type);
String strSessionId = "" + byteArrayToInt(sessionId, 0);
String bufferName = strSessionId + "_" + type[0];
String flagName = bufferName + "_F";
// Check if Session is alive
if (context.getAttribute("VNCProxySession_" + strSessionId) == null) {
response.setContentLength(0);
dos.flush();
return;
}
if (data.length == 0) {
// READ FROM HUB
// Wait for the buffer data to be ready
long timeOutBegin = System.currentTimeMillis();
while (context.getAttribute(flagName) == null && (System.currentTimeMillis() - timeOutBegin) < TIMEOUT) {
try {
Thread.sleep(SLEEP_TIME);
} catch (Exception e) {
System.err.println("Error while sleeping : " + e.getMessage());
}
}
// Detects time-out
if ((System.currentTimeMillis() - timeOutBegin) > TIMEOUT) { return; }
if (context.getAttribute(bufferName) != null) {
// Send back buffer
byte[] returnedBuffer = (byte[]) context.getAttribute(bufferName);
response.setContentLength(returnedBuffer.length);
dos.write(returnedBuffer);
context.removeAttribute(bufferName);
context.removeAttribute(flagName);
} else {
response.setContentLength(0);
}
} else {
// SEND TO HUB
dis.readFully(data);
if (data.length > 0) {
// Wait for the buffer to be released before putting data in it
long timeOutBegin = System.currentTimeMillis();
while (context.getAttribute(flagName) != null && (System.currentTimeMillis() - timeOutBegin) < TIMEOUT) {
try {
Thread.sleep(SLEEP_TIME);
} catch (Exception e) {
System.err.println("Error while sleeping : " + e.getMessage());
}
}
// Detects time-out
if ((System.currentTimeMillis() - timeOutBegin) > TIMEOUT) { return; }
// Put data in buffer
context.setAttribute(bufferName, data);
context.setAttribute(flagName, "f");
}
response.setContentLength(0);
}
// Send response in any case
dos.flush();
// Statistics on the session
updateSession(context, strSessionId, data);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* This method overrides the Servlet doGet method. It is just there for
* testing purpose.
*
* @param request
* The HTTP request
* @param response
* The HTTP response
* @throws IOException
* Any IO Exception
* @throws ServletException
* Any Servlet Exception
*/
@Override
public final void doGet(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
DataOutputStream dos = new DataOutputStream(response.getOutputStream());
StringBuffer sb = new StringBuffer();
sb.append("I'm VNCPHubListener...");
dos.write(sb.toString().getBytes());
dos.flush();
}
/**
* This method is used to update the current VNCPSession (data length
* transfered + last move update time)
*
* @param context
* The Servlet request context
* @param sessionId
* The current sessionId
* @param data
* The data byte array handled by the VNCPHubListener
*/
public final synchronized void updateSession(final ServletContext context, final String sessionId, final byte[] data) {
VNCPSession theSession = (VNCPSession) context.getAttribute("VNCProxySession_" + sessionId);
if (theSession != null) {
theSession.setLastMove(System.currentTimeMillis());
theSession.setNbRequests(theSession.getNbRequests() + 1);
theSession.setDataSize(theSession.getDataSize() + data.length);
context.setAttribute("VNCProxySession_" + sessionId, theSession);
}
}
/**
* This method returns the integer value of an integer byte array
* representation
*
* @param b
* The input byte array
* @param offset
* The offset
* @return The integer value of the given byte array
*/
private int byteArrayToInt(final byte[] b, final int offset) {
return (b[0] << NUMBER_24) + ((b[1] & NUMBER_0XFF) << NUMBER_16) + ((b[2] & NUMBER_0XFF) << NUMBER_8) + (b[NUMBER_3] & NUMBER_0XFF);
}
}