All Downloads are FREE. Search and download functionalities are using the official Maven repository.

es.gob.afirma.standalone.protocol.ServiceInvocationManager Maven / Gradle / Ivy

There is a newer version: 1.8.2
Show newest version
/* Copyright (C) 2011 [Gobierno de Espana]
 * This file is part of "Cliente @Firma".
 * "Cliente @Firma" 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 2 of the License, or (at your option) any later version.
 *   - or The European Software License; either version 1.1 or (at your option) any later version.
 * You may contact the copyright holder at: [email protected]
 */

package es.gob.afirma.standalone.protocol;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.BindException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.net.ssl.SSLSocket;
import javax.swing.Timer;

import es.gob.afirma.core.misc.Base64;
import es.gob.afirma.core.misc.Platform;
import es.gob.afirma.keystores.mozilla.apple.AppleScript;
import es.gob.afirma.standalone.AutoFirmaUtil;

/** Gestor de la invocación por socket. */
public final class ServiceInvocationManager {

	static final Logger LOGGER = Logger.getLogger("es.gob.afirma"); //$NON-NLS-1$

	private static final int READ_BUFFER_SIZE = 2048;

	/** Tamaño del segmento de cada fragmento de datos que se lea del socket que se mantendrá
	 * almacenado por si el segmento de fin de entrada queda dividido entre dos fragmentos. El valor
	 * permite que quepa al completo en un subfragmento la etiqueta EOF y la etiqueta idSession con
	 * su valor. */
	private static final int BUFFERED_SECURITY_RANGE = 36;

	/** Número máximo de intentos de lectura consecutivos en el buffer sin que se encuentren datos. */
	private static final int MAX_READING_BUFFER_TRIES = 10;

	/** Tiempo de espera de cada socket en milisegundos. */
	private static int SOCKET_TIMEOUT = 90000;

	/** Máximo numero de caracteres que podemos enviar en una respuesta. */
	private static final int RESPONSE_MAX_SIZE = 1000000;
	//  peticiones que podemos recibir
	private static final String CMD = "cmd="; //$NON-NLS-1$
	private static final String ECHO = "echo="; //$NON-NLS-1$
	private static final String FRAGMENT = "fragment="; //$NON-NLS-1$
	private static final String SEND = "send="; //$NON-NLS-1$
	private static final String SIGN = "firm="; //$NON-NLS-1$
	private static final String AFIRMA = "afirma://" ; //$NON-NLS-1$
	private static final String AFIRMA2 = "afirma://service?" ; //$NON-NLS-1$
	private static final String AFIRMA3 = "afirma://service/?" ; //$NON-NLS-1$
	private static final String SAVE = "afirma://save?"; //$NON-NLS-1$
	private static final String SAVE2 = "afirma://save/?"; //$NON-NLS-1$

	/** Parámetro de entrada con la versión del protocolo que se va a utilizar. */
	private static final String PROTOCOL_VERSION_PARAM = "v"; //$NON-NLS-1$

	/** Versión de protocolo más avanzada soportada. */
	private static final int CURRENT_PROTOCOL_VERSION = 1;

	/** Listado de versiones de protocolo soportadas. */
	private static final int[] SUPPORTED_PROTOCOL_VERSIONS = new int[] { CURRENT_PROTOCOL_VERSION };

	// cadenas usadas dentro de las peticiones
	private static final String RESET = "-" ; //$NON-NLS-1$
	private static final String SEPARADOR = "@" ;   //$NON-NLS-1$
	private static final String EOF = SEPARADOR+"EOF" ; //$NON-NLS-1$
	private static final String IDSESSION = "idsession"; //$NON-NLS-1$
	// respuesta que podemos mandar.
	private static final String MORE_DATA_NEED = "MORE_DATA_NEED"; //$NON-NLS-1$
	private static final String OK = "OK"; //$NON-NLS-1$
	private static final String SAVE_OK = "SAVE_OK"; //$NON-NLS-1$
	private static final String CANCEL = "CANCEL"; //$NON-NLS-1$
	private static final String MEMORY_ERROR = "MEMORY_ERROR";//$NON-NLS-1$
	// ip locales para que no nos vengan peticiones externas
	private static final String LOCALHOST = "localhost"; //$NON-NLS-1$
	private static final String LOOP_DIR = "127.0.0.1";  //$NON-NLS-1$
	private static final String LOOP_DIR_2 = "0:0:0:0:0:0:0:1";  //$NON-NLS-1$
	// parametros para carga del certificado SSL
	private static final String KSPASS = "654321"; //$NON-NLS-1$
	private static final String CTPASS = "654321"; //$NON-NLS-1$
	private static final String KEYSTORE_NAME = "autofirma.pfx"; //$NON-NLS-1$
	private static final String PKCS12 = "PKCS12"; //$NON-NLS-1$
	private static final String KEY_MANAGER_TYPE = "SunX509"; //$NON-NLS-1$
	private static final String SSLCONTEXT = "TLSv1"; //$NON-NLS-1$

	/** Temporizador para cerrar la aplicación cuando pase un tiempo de inactividad. */
	private final static Timer timer = new Timer(SOCKET_TIMEOUT, evt -> {
		LOGGER.warning("Se ha caducado la conexion. Se deja de escuchar en el puerto..."); //$NON-NLS-1$
		if (Platform.OS.MACOSX.equals(Platform.getOS())) {
			closeMacService();
		}
		System.exit(-4);
	});

	/** Mata el proceso de AutoFirma cuando estamos en macOS. En el resto de sistemas
	 * no hace nada. */
	public static void closeMacService() {
		LOGGER.warning("Ejecuto kill"); //$NON-NLS-1$
		final AppleScript script = new AppleScript(
				"kill -9 $(ps -ef | grep " + idSession + " | awk '{print $2}')"  //$NON-NLS-1$ //$NON-NLS-2$
				);
		try {
			script.run();
		}
		catch (final Exception e) {
			LOGGER.warning("No se ha podido cerrar la aplicacion: " + e); //$NON-NLS-1$
		}
	}

	/** Coge el foco del sistema en macOS. En el resto de sistemas no hace nada. */
	public static void focusApplication() {
		if (Platform.OS.MACOSX.equals(Platform.getOS())) {
			final String scriptCode = "tell me to activate"; //$NON-NLS-1$
			final AppleScript script = new AppleScript(scriptCode);
			try {
				script.run();
			}
			catch (final Exception e) {
				LOGGER.warning("Fallo cogiendo el foco en macOS: " + e); //$NON-NLS-1$
			}
		}
	}

	private final static List request = new ArrayList<>();
	private final static List toSend = new ArrayList<>();
	private static int parts = 0;
	private static String idSession ;

	/** Constructor vacío privado para que no se pueda instanciar la clase ya que es estático. */
	private ServiceInvocationManager(){
		// No instanciable
	}

	/** Inicia el servicio. Se intenta establecer un socket que escuche en el puerto pasado por la URL.
	 * @param url URL (debe indicarse el puerto).
	 * @throws UnsupportedProtocolException Si no se soporta el protocolo o la versión de este. */
	static void startService(final String url) throws UnsupportedProtocolException {

		checkSupportProtocol(getVersion(url));

		try {
			// ruta de la que debe buscar el fichero
			final File sslKeyStoreFile = getKeyStoreFile();
			if (sslKeyStoreFile == null) {
				throw new KeyStoreException(
					"No se encuentra el almacen para el cifrado de la comunicacion SSL" //$NON-NLS-1$
				);
			}

			LOGGER.info("Se utilizara el siguiente almacen para establecer el socket SSL: " + sslKeyStoreFile.getAbsolutePath()); //$NON-NLS-1$

			// pass del fichero
			final char ksPass[] = KSPASS.toCharArray();
			final char ctPass[] = CTPASS.toCharArray();
			// generamos el key store desde el fichero del certificado, de tipo PKCS12
			final KeyStore ks = KeyStore.getInstance(PKCS12);
			ks.load(new FileInputStream(sslKeyStoreFile), ksPass);
			// key manager factory de tipo SunX509
			final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KEY_MANAGER_TYPE);
			kmf.init(ks, ctPass);

			final SSLContext sc = SSLContext.getInstance(SSLCONTEXT);
			sc.init(kmf.getKeyManagers(), null, null);
			LOGGER.info("Iniciando servicio local de firma: " + url); //$NON-NLS-1$
			final SSLServerSocketFactory ssocketFactory = sc.getServerSocketFactory();
			final SSLServerSocket ssocket = tryPorts(getPorts(url), ssocketFactory);
			ssocket.setReuseAddress(true);

			// empieza la cuenta atras del temporizador.
			timer.start();

			while (true){
				try {

					final SSLSocket sslSocket = (SSLSocket) ssocket.accept();

					LOGGER.info("Detectada conexion entrante"); //$NON-NLS-1$

					// comprobamos que la direccion es local. Si no es local se descarta la peticion
					if (!isLocalAddress((InetSocketAddress) sslSocket.getRemoteSocketAddress())) {
						sslSocket.close();
						ssocket.close();
						LOGGER.severe(
							"Se ha detectado un acceso no autorizado desde " + //$NON-NLS-1$
								((InetSocketAddress) sslSocket.getRemoteSocketAddress()).getHostString()
						);
						continue;
					}
					new Thread(() ->  {
						try {
							final String httpRequest = read(sslSocket.getInputStream());

							// comprobamos que la peticion no es vacia
							if (httpRequest == null) {
								LOGGER.warning("Se ha recibido una peticion vacia"); //$NON-NLS-1$
							}
							else {
								LOGGER.fine("Peticion HTTP recibida:\n" + httpRequest); //$NON-NLS-1$
								getCommandUri(httpRequest, sslSocket);
							}
						}
						catch (final IllegalArgumentException e) {
							LOGGER.severe(
								"Los parametros recibidos a traves del socket no son validos, se ignorara la peticion: " + e //$NON-NLS-1$
							);
							return;
						}
						catch(final IOException e) {
							LOGGER.severe(
								"Error en el envio a traves del socket: " + e //$NON-NLS-1$
							);
							return;
						}
					}).start();

				}
				catch (final SocketTimeoutException e) {
					LOGGER.severe("Tiempo de espera del socket terminado: " + e); //$NON-NLS-1$
				}
			}
		}

		// Con las excepciones no hacemos nada ya que no tenemos forma de transmitir el
		// error de vuelta y no debemos mostrar dialogos graficos
		catch (final IOException e) {
			LOGGER.log(Level.SEVERE, "Error en la comunicacion a traves del socket", e); //$NON-NLS-1$
		}
		catch(final KeyStoreException e){
            LOGGER.severe("Error con el keyStore: " + e); //$NON-NLS-1$
		}
        catch(final NoSuchAlgorithmException e){
            LOGGER.severe("Error con el algorimto del  certificado: " + e); //$NON-NLS-1$
        }
        catch(final CertificateException e){
            LOGGER.severe("Error con el certificado: " + e); //$NON-NLS-1$
        }
        catch(final UnrecoverableKeyException e){
            LOGGER.severe("Error al recuperar la key: " + e); //$NON-NLS-1$
        }
        catch(final KeyManagementException e){
            LOGGER.severe("Error con el KeyManager: " + e); //$NON-NLS-1$
        }

	}

	/** Obtiene el fichero del almacén con la clave SSL de alguno de los directorios
	 * del sistema en los que puede estar.
	 * @return Almacén de claves o {@code null} si no se encontró. */
	private static File getKeyStoreFile() {

		File appDir = AutoFirmaUtil.getApplicationDirectory();

		if (appDir != null && new File(appDir, KEYSTORE_NAME).exists()) {
			return new File(appDir, KEYSTORE_NAME);
		}

		if (Platform.getOS() == Platform.OS.WINDOWS) {
			appDir = AutoFirmaUtil.getWindowsAlternativeAppDir();
			if (appDir != null && new File(appDir, KEYSTORE_NAME).exists()) {
				return new File(appDir, KEYSTORE_NAME);
			}
		}
		else if (Platform.getOS() == Platform.OS.LINUX) {
			appDir = AutoFirmaUtil.getLinuxAlternativeAppDir();
			if (appDir != null && new File(appDir, KEYSTORE_NAME).exists()) {
				return new File(appDir, KEYSTORE_NAME);
			}
		}
		else if (Platform.getOS() == Platform.OS.MACOSX) {
			appDir = AutoFirmaUtil.getMacOsXAlternativeAppDir();
			if (new File(appDir, KEYSTORE_NAME).exists()) {
				return new File(appDir, KEYSTORE_NAME);
			}
		}

		return null;
	}

	/** Manda los datos de respuesta a la aplicación.
	 * @param response Respuesta al envío.
	 * @param socket Socket a donde mandar la respuesta.
	 * @param petition Petición que se manda, para registrarla en el log.
	 * @throws IOException Si hay errores en el envío. */
	private static void sendData(final byte[] response, final Socket socket, final String petition) throws IOException {
		socket.getOutputStream().write(response);
		socket.getOutputStream().flush();
		LOGGER.info("Mandando respuesta a la peticion: " + petition);  //$NON-NLS-1$
		// volvemos a activar el timer
		timer.restart();
	}

	/** Crea una respuesta HTTP para enviar a traves del socket.
	 * @param ok Indica si la operacion finalizó bien o mal.
	 * @param response La respuesta que se mandará en el HTTP.
	 * @return Devuelve el byte array con la respuesta en formato HTTP. */
	private static byte[] createHttpResponse(final boolean ok, final String response) {
		final StringBuilder sb = new StringBuilder();
		if (ok) {
			sb.append("HTTP/1.1 200 OK\n"); //$NON-NLS-1$
		}
		else  {
			sb.append("HTTP/1.1 500 Internal Server Error"); //$NON-NLS-1$
		}
		sb.append("Connection: close\n"); //$NON-NLS-1$
		sb.append("Pragma: no-cache\n"); //$NON-NLS-1$
		sb.append("Server: Cliente @firma\n"); //$NON-NLS-1$
		sb.append("Content-Type: text/html; charset=utf-8\n"); //$NON-NLS-1$
		sb.append("Access-Control-Allow-Origin: *\n"); //$NON-NLS-1$
		sb.append('\n');
		if (response != null) {
			sb.append(Base64.encode(response.getBytes(), true));
		}
		return sb.toString().getBytes();
	}

	/** Obtiene los puertos que se deben probar para la conexión externa.
	 * Asigna cual es la clave.
	 * @param url URL de la que extraer los puertos.
	 * @return Listados de puertos. */
	private static int[] getPorts(final String url) {
		final URI u;
		try {
			u = new URI(url);
		}
		catch (final Exception e) {
			throw new IllegalArgumentException("La URI (" + url + ") de invocacion no es valida: " + e); //$NON-NLS-1$ //$NON-NLS-2$
		}
		final String query = u.getQuery();
		checkNullParameter(query, "La URI de invocacion no contiene parametros: " + url); //$NON-NLS-1$
		final Properties p = new Properties();
		try {
			p.load(new ByteArrayInputStream(query.replace("&", "\n").getBytes())); //$NON-NLS-1$ //$NON-NLS-2$
		}
		catch (final IOException e) {
			throw new IllegalArgumentException(
				"Los parametros de la URI de invocacion no estan el el formato correcto: " + url, //$NON-NLS-1$
				e);
		}
		final String ps = p.getProperty("ports"); //$NON-NLS-1$
		checkNullParameter(ps, "La URI de invocacion no contiene el parametro 'ports': " + url); //$NON-NLS-1$
		final String[] ports = ps.split(","); //$NON-NLS-1$
		final int[] ret = new int[ports.length];
		for (int i=0; isocket en los puertos que se pasan por parámetro.
	 * @param ports Puertos a probar.
	 * @param socket Socket que se intenta conectar.
	 * @return El SSLServerSocket ya creado en el primer puerto encontrado disponible.
	 * @throws IOException Si ocurren errores durante el intento. */
	private static SSLServerSocket tryPorts(final int[] ports, final SSLServerSocketFactory socket) throws IOException {
		checkNullParameter(ports, "La lista de puertos no puede ser nula"); //$NON-NLS-1$
		checkNullParameter(socket, "El socket servidor no puede ser nulo"); //$NON-NLS-1$
		for (final int port : ports) {
			try {
				final SSLServerSocket ssocket = (SSLServerSocket) socket.createServerSocket(port);
				LOGGER.info("Establecido el puerto " + port + " para el servicio Cliente @firma"); //$NON-NLS-1$ //$NON-NLS-2$
				return ssocket;
			}
			catch (final BindException e) {
				LOGGER.warning(
					"El puerto " + port + " parece estar en uso, se continua con el siguiente: " + e //$NON-NLS-1$ //$NON-NLS-2$
				);
			}
			catch(final Exception e) {
				LOGGER.warning(
					"No se ha podido conectar al puerto " + port + ", se intentara con el siguiente: " + e //$NON-NLS-1$ //$NON-NLS-2$
				);
			}
		}
		throw new IOException("No se ha podido ligar el socket servidor a ningun puerto"); //$NON-NLS-1$
	}

	/** Lee los datos recibidos en el socket. Si se recibe una petición sin datos o compuesto de
	 * caracteres blancos (como ocurre cuando el certificado SSL no es correcto) se devuelve nulo.
	 * @param socketIs Flujo de entrada de datos del socket.
	 * @return Los datos recibidos o {@code null} si se recibe una petición vacía.
	 * @throws IOException Si ocurren errores durante la lectura. */
	private static String read(final InputStream socketIs) throws IOException {
		// Buffer en el que se ira almacenando toda la entrada
		final StringBuilder data = new StringBuilder();
		// Cadena que se ira actualizando para contener siempre la union de los
		// 2 ultimos fragmentos con un tamano suficiente para contener el
		// fragmento de fin y el identificador de sesion
		String subFragment = ""; //$NON-NLS-1$
		final byte[] reqBuffer = new byte[READ_BUFFER_SIZE];
		boolean readed = true;
		String insert;
		// Limite alcanzado de lecturas vacias del socket
		int readingTries;
		while (readed) {

			// Leemos del socket hasta obtener algo mas que caracteres vacios
			readingTries = 0;
			do {
				readingTries++;
				socketIs.read(reqBuffer);
				insert = new String(reqBuffer, StandardCharsets.UTF_8);
			} while (insert.trim().isEmpty() && readingTries <= MAX_READING_BUFFER_TRIES);

			// Para evitar un error de memoria, si llegamos al maximo numero de intentos en el
			// que solo hemos leido caracteres vacios, entendemos que era una peticion invalida
			// y develvemos nulo para que se ignore
			if (readingTries > MAX_READING_BUFFER_TRIES) {
				return null;
			}

			subFragment += insert.substring(0, Math.min(BUFFERED_SECURITY_RANGE, insert.length()));

			// Comprobamos si la etiqueta de fin se encuentra en el nuevo fragmento o si esta a medias
			// entre este y el anterior. En base a eso, tendremos que agregar o quitar datos del buffer
			if (subFragment.indexOf(EOF) != -1 || insert.indexOf(EOF) != -1) {

				readed = false;

				final boolean findOnSubFragment;
				int eofPos;
				int idSessionPos;
				String requestSessionId = null;

				// Si se detecta el fragmento de fin en la union de ambos fragmentos,
				// comprobamos si parte del contenido del buffer sobra o si hay algo que agregar
				if (subFragment.indexOf(EOF) != -1) {
					findOnSubFragment = true;
					eofPos = subFragment.indexOf(EOF);
					idSessionPos = subFragment.indexOf(IDSESSION);
					if (idSessionPos != -1) {
						requestSessionId = subFragment.substring(
								idSessionPos + IDSESSION.length() + 1,
								eofPos);
					}
				}
				else {
					findOnSubFragment = false;
					eofPos = insert.indexOf(EOF);
					idSessionPos = insert.indexOf(IDSESSION);
					if (idSessionPos != -1) {
						requestSessionId = insert.substring(
								idSessionPos + IDSESSION.length() + 1,
								eofPos);
					}
				}

				// Comprobamos que el id de sesion transmitido es correcto
				checkIdSession(requestSessionId);

				if (findOnSubFragment) {
					// Si el id de session es anterior al rango de seguridad, en el buffer de
					// la peticion se habra introducido parte de este identificador y hay que
					// borrarlos.
					if (idSessionPos != -1 && idSessionPos < BUFFERED_SECURITY_RANGE) {
						data.replace(data.length() - (BUFFERED_SECURITY_RANGE - idSessionPos), data.length(), ""); //$NON-NLS-1$
					}
					// Si la posicion del EOF es anterior al rango de seguridad, entonces
					// en el buffer de la peticion se ha introducido parte de esta etiqueta
					// y hay que borrarla.
					else if (eofPos < BUFFERED_SECURITY_RANGE) {
						data.replace(data.length() - (BUFFERED_SECURITY_RANGE - eofPos), data.length(), ""); //$NON-NLS-1$
					}
					// Si tanto el EOF como el id de sesion (en caso de haber) son posteriores
					// al rango de seguridad, solo habra que incorporar los datos encontrados
					// antes de estas posiciones (siempre y cuando no esten al principio, en
					// cuyo caso, no hay que agregar nada)
					else if (idSessionPos > 0 || eofPos > 0) {
						data.append(subFragment.substring(0, idSessionPos > -1 ? idSessionPos : eofPos));
					}
				}
				else {
					data.append(insert.substring(0, idSessionPos > -1 ? idSessionPos : eofPos));
				}
			}
			else {
				// Agregamos los datos al total, ya que no se ha encontrado la cadena de fin de lectura
				data.append(insert);
				subFragment = insert.substring(Math.max(0, insert.length() - BUFFERED_SECURITY_RANGE));
			}
		}
		return data.toString();
	}

	/** Comprueba que la dirección que intenta conectarse es local.
	 * @param a Dirección a probar.
	 * @return true si es dirección local, false en caso contrario. */
	private static boolean isLocalAddress(final InetSocketAddress a) {
		final String hostString = a.getHostString();
		if (LOOP_DIR_2.equals(hostString) ||
			LOOP_DIR.equals(hostString) ||
			LOCALHOST.equals(hostString)) {
			return true;
		}
		return false;
	}

	/** Analiza cual el ComandUri de la petición recibida y realiza sus operaciones pertinentes.
	 * @param httpRequest Petición http recibida de la que hay que extraer el command uri.
* Las peticiones permitidas son: *
    *
  • cmd= Iniciar una operación que viene sin fragmentar.
  • *
  • echo= Petición echo para comprobar que la aplicación esta lista
  • *
  • fragment= Inicia el envío de los datos fragmentándolos en varias peticiones.
  • *
  • firm= Inicia una operación juntando los datos fragmentados de las peticiones anteriores.
  • *
  • send= Envía la respuesta de la una operación realizada. Si es demasiado grande se fragmenta en varios envíos.
  • *
* @param socket El Socket en el que se escucha la petición. * @throws IOException Si hay error en el tratamiento de datos. */ private static void getCommandUri(final String httpRequest, final Socket socket) throws IOException { checkNullParameter(httpRequest, "Los datos recibidos por HTTP son nulos"); //$NON-NLS-1$ final String uriType = getUriTypeFromRequest(httpRequest); LOGGER.info("Recibido comando de tipo: " + uriType); //$NON-NLS-1$ try { switch (uriType) { case ECHO: doEchoPetition(httpRequest.substring(httpRequest.indexOf(ECHO) + ECHO.length()), socket); break; case CMD: doCmdPetition(httpRequest.substring(httpRequest.indexOf(CMD) + CMD.length()), socket); break; case FRAGMENT: doFragmentPetition(httpRequest.substring(httpRequest.indexOf(FRAGMENT) + FRAGMENT.length()), socket); break; case SIGN: doFragmentedProcess(socket); break; case SEND: doSendPetition(httpRequest.substring(httpRequest.indexOf(SEND) + SEND.length()), socket); break; default: // Nunca deberia entrar aqui throw new IllegalStateException("Comando no permitido: " + uriType); //$NON-NLS-1$ } } catch (final OutOfMemoryError e){ LOGGER.severe("Se ha producido un error por falta memoria de la maquina virtual: " + e); //$NON-NLS-1$ sendData(createHttpResponse(true, MEMORY_ERROR), socket, "Error de memoria"); //$NON-NLS-1$ } catch(final Exception e) { throw new IllegalArgumentException( "Error al procesar el comando de tipo '" + uriType + "': " + e, e //$NON-NLS-1$ //$NON-NLS-2$ ); } socket.close(); } /** Devuelve el uriType de la petición recibida. Lanza una excepción en caso * de que la petición no sea petición. * @param httpRequest La petición a tratar. * @return String uriType de la petición. */ private static String getUriTypeFromRequest(final String httpRequest) { final String[] supportedUriTypes = new String[] {CMD, ECHO, FRAGMENT, SIGN, SEND}; int i = 0; String uriType = null; while (uriType == null && i < supportedUriTypes.length) { if (httpRequest.indexOf(supportedUriTypes[i]) != -1) { uriType = supportedUriTypes[i]; } i++; } if (uriType == null) { throw new IllegalArgumentException( "Los datos recibidos por HTTP no contienen comando reconocido: " + httpRequest //$NON-NLS-1$ ); } return uriType; } /** Realiza las acciones pertinentes en caso de que la petición contenga una petición echo. * Se reinician las variables de control de la aplicación para desechar cualquier petición anterior. * @param cmd Comando URI. * @param socket Socket donde se recibe la petición. * @throws IOException Si hay error en el envío de datos. */ private static void doEchoPetition(final String cmd, final Socket socket) throws IOException { // paramos el timer mientras la aplicacion realiza operaciones timer.stop(); // solo reseteamos las variables de control si la peticion es echo=- en caso de que sea un echo= simplemente respondemos if (cmd.contains(RESET)){ reset(); } LOGGER.info("Comando URI recibido por HTTP: " + ECHO); //$NON-NLS-1$ sendData(createHttpResponse(true, OK), socket, ECHO); } /** Realiza la operación que corresponda cuando ya se han recibido todos los * fragmentos de la petición (firm=). * @param socket Socket donde se recibe la petición. * @throws IOException Si hay error en la lectura o envío de datos. */ private static void doFragmentedProcess(final Socket socket) throws IOException { // paramos el timer mientras la aplicacion realiza operaciones boolean isSave = false; timer.stop(); LOGGER.info("Comando URI recibido por HTTP: " + SIGN); //$NON-NLS-1$ // en caso de que sea la primera vez que lo ejecutamos, realizamos la operacion. // si la respuesta no llega al JS y vuelve a realizar la misma peticion, ya tenemos // formada la respuesta y solo hay que devolver el numero de peticiones. if (toSend.isEmpty()) { final StringBuilder totalhttpRequest = new StringBuilder(); for(final String object: request){ totalhttpRequest.append(object); LOGGER.info("PETICION PROCESADA: " + totalhttpRequest); //$NON-NLS-1$ } // Si la operacion es de guardado (Respuesta fija) if (totalhttpRequest.toString().startsWith("afirma://save?") || totalhttpRequest.toString().startsWith("afirma://save/?")){ //$NON-NLS-1$ //$NON-NLS-2$ isSave = true; final String operationResult = ProtocolInvocationLauncher.launch(totalhttpRequest.toString(), true); if (operationResult.equals(OK)){ sendData(createHttpResponse(true, SAVE_OK), socket, "Operacion save realizada con exito"); //$NON-NLS-1$ } else if (operationResult.equals(CANCEL)){ sendData(createHttpResponse(true, CANCEL), socket, "Cancelado por el usuario"); //$NON-NLS-1$ } else { throw new IllegalArgumentException( "Error al realizar la operacion save" //$NON-NLS-1$ ); } } // Si hay que devolver el valor obtenido else { final String operationResult = ProtocolInvocationLauncher.launch(totalhttpRequest.toString(), true); calculateNumberPartsResponse(operationResult); } } else { LOGGER.info("Se habia calculado el numero de partes anteriormente"); //$NON-NLS-1$ } // si no es una operacion save y nos vuelven a pedir una parte. if (!isSave){ sendData( createHttpResponse(true, Integer.toString(parts)), socket, "Se mandaran " + parts + " partes" //$NON-NLS-1$//$NON-NLS-2$ ); } } /** Realiza las acciones pertinentes en caso de que la petición contenga una petición cmd=. * @param cmd Valor del parámetro cmd. * @param socket Socket donde se recibe la petición. * @throws IOException Error en la lectura o en el envío de datos. */ private static void doCmdPetition (final String cmd, final Socket socket) throws IOException{ final String cmdUri = new String(Base64.decode(cmd.trim(), true)); if (cmdUri.startsWith(AFIRMA) && !(cmdUri.startsWith(AFIRMA2) || cmdUri.startsWith(AFIRMA3))) { // paramos el timer mientras la aplicacion realiza operaciones timer.stop(); LOGGER.info("Comando URI recibido por HTTP: " + cmdUri); //$NON-NLS-1$ if (cmdUri.startsWith(SAVE) || cmdUri.startsWith(SAVE2)) { final String operationResult = ProtocolInvocationLauncher.launch(cmdUri.toString(), true); if (operationResult.equals(OK)) { sendData(createHttpResponse(true, SAVE_OK), socket, "save"); //$NON-NLS-1$ } else if (operationResult.equals(CANCEL)) { sendData(createHttpResponse(true, CANCEL), socket, "Cancelado por el usuario"); //$NON-NLS-1$ } else { throw new IllegalArgumentException( "Error al realizar la operacion 'save'" //$NON-NLS-1$ ); } } else { if (toSend.isEmpty()){ // Usamos la url que acabamos de recibir sin fragmentar final String operationResult = ProtocolInvocationLauncher.launch(cmdUri.toString(), true); calculateNumberPartsResponse(operationResult); } sendData(createHttpResponse(true, Integer.toString(parts)), socket, "Se mandaran " + parts + " partes"); //$NON-NLS-1$ //$NON-NLS-2$ } } else { throw new IllegalArgumentException( "Los datos recibidos en el parametro 'cmd' por HTTP no son una URI del tipo 'afirma://': " + cmdUri //$NON-NLS-1$ ); } } /** Realiza las acciones pertinentes en caso de que la petición contenta una petición fragment=. * @param fragment httpRequest URL de la que hay que substraer el commandUri. * @param socket Socket donde se recibe la petición. * @throws IOException Si hay error en el tratamiento de datos. */ private static void doFragmentPetition (final String fragment, final Socket socket) throws IOException{ // paramos el timer mientras la aplicacion realiza operaciones timer.stop(); LOGGER.info("Comando URI recibido por HTTP: " + fragment); //$NON-NLS-1$ final String[] petition = fragment.split(SEPARADOR); final int part = Integer.parseInt(petition [1]); final int partTotal = Integer.parseInt(petition [2]); final String save = new String (Base64.decode(petition[3].trim(), true)); if (request.size() == part ){ LOGGER.info("sustituimos la parte " + part); //$NON-NLS-1$ request.set(part-1, save); } else { LOGGER.info("insertamos la parte " + part); //$NON-NLS-1$ request.add(part-1, save); } if (part == partTotal){ sendData(createHttpResponse(true, OK), socket, "Mandada la ultima parte " + part +"de " + partTotal); //$NON-NLS-1$ //$NON-NLS-2$ } else { sendData(createHttpResponse(true, MORE_DATA_NEED), socket, "Mandar resto de datos de la firma, parte " + part + "de " + partTotal); //$NON-NLS-1$//$NON-NLS-2$ } } /** Realiza el envío de datos. * @param send Configuración para el envío de datos. * @param socket Socket donde se recibe la petición. * @throws IOException Si hay error en el tratamiento de datos. */ private static void doSendPetition (final String send, final Socket socket) throws IOException { // paramos el timer mientras la aplicacion realiza operaciones timer.stop(); LOGGER.info("Comando URI recibido por HTTP: " + send); //$NON-NLS-1$ final String[] petition = send.split(SEPARADOR); final int part = Integer.parseInt(petition [1]); final int partTotal = Integer.parseInt(petition [2]); if (part < 1 || part > partTotal) { throw new IllegalArgumentException( "Se ha solicitado enviar un fragmento invalido: " + part + "de " + partTotal //$NON-NLS-1$ //$NON-NLS-2$ ); } sendData(createHttpResponse(true, toSend.get(part-1)), socket, "Mandada la parte " + part + " de " + partTotal); //$NON-NLS-1$ //$NON-NLS-2$ } /** Calcula en cuantas partes hay que realizar el envío de la operación y divide la respuesta en dichas partes. * @param operationResult La operación resultante que hay que dividir. */ private static void calculateNumberPartsResponse(final String operationResult) { parts = (int) Math.ceil((double)operationResult.length() / (double) RESPONSE_MAX_SIZE); LOGGER.info("Se mandaran " + parts + "partes"); //$NON-NLS-1$//$NON-NLS-2$ LOGGER.info("tam total=" + operationResult.length()); //$NON-NLS-1$ // si recibimos la misma peticion otra vez ya tenemos los datos preparados, solo devolvemos las peticiones int offset; for (int i = 0; i < parts; i++){ offset = RESPONSE_MAX_SIZE * i; toSend.add(operationResult.substring(offset, Math.min(offset + RESPONSE_MAX_SIZE, operationResult.length()))); LOGGER.info("Tamano de la parte " + (i+1) + " =" + toSend.get(i).length()); //$NON-NLS-1$ //$NON-NLS-2$ } } /** Comprueba que un parámetro no sea nulo. * @param parameter Parámetro que se debe comprobar que no sea nulo. * @param excepcionText Texto que se debe lanzar con la excepción. */ private static void checkNullParameter (final Object parameter, final String excepcionText){ if (parameter == null) { throw new IllegalArgumentException(excepcionText); } } /** Reinicia las variables de control si se realiza una nueva llamada y hay que descartar * todas las las operaciones pendientes. */ private static void reset(){ request.clear(); toSend.clear(); parts = 0; } /** Comprueba que el idSession de la petición recibida coincida con el * idSession generado al abrir la aplicación por socket. * @param requestSessionId Identificador de sesión enviado en la petición. */ private static void checkIdSession (final String requestSessionId) { // se esperaba un idSession y no se ha recibido if (idSession != null && !idSession.equals(requestSessionId)) { throw new IllegalArgumentException("No se ha recibido el idSession esperado."); //$NON-NLS-1$ } } /** Comprueba si una versión de protocolo está soportado por la implementación actual. * @param protocolId Identificador de la versión del protocolo. * @throws UnsupportedProtocolException Cuando la versión de protocolo utilizada no se encuentra * entre las soportadas. */ private static void checkSupportProtocol(final String protocolId) throws UnsupportedProtocolException { int protocolVersion = 1; if (protocolId != null) { try { protocolVersion = Integer.parseInt(protocolId.trim()); } catch (final Exception e) { LOGGER.info( "El ID de protocolo indicado no es un numero entero (" + protocolId + "): " + e //$NON-NLS-1$ //$NON-NLS-2$ ); protocolVersion = -1; } } for (final int version : SUPPORTED_PROTOCOL_VERSIONS) { if (version == protocolVersion) { return; } } throw new UnsupportedProtocolException(protocolVersion, protocolVersion > CURRENT_PROTOCOL_VERSION); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy