gt: 		final FileChannel in = is.getChannel();
context: /*
   * Copyright 2009-2018 Rubrica
   *
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU Lesser General Public License as published by
   * the Free Software Foundation, either version 3 of the License, or
   * (at your option) any later version.
   *
   * This program 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 Lesser General Public License for more details.
   *
   * You should have received a copy of the GNU Lesser General Public License
   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
   */
  
  package io.rubrica.core;
  
  import java.io.BufferedInputStream;
  import java.io.ByteArrayOutputStream;
  import java.io.File;
  import java.io.FileInputStream;
  import java.io.FileOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.nio.MappedByteBuffer;
  import java.nio.channels.FileChannel;
  import java.security.cert.X509Certificate;
  import java.util.ArrayList;
  import java.util.Locale;
  import java.util.logging.Logger;
  
  /**
   * M&eacute;todos generales de utilidad para toda la aplicaci&oacute;n.
   */
  public final class Util {
  
  	private static final int BUFFER_SIZE = 4096;
  	private static final String[] SUPPORTED_URI_SCHEMES = new String[] { "http", "https", "file", "urn" };
  	private static final String DEFAULT_ENCODING = "utf-8";
  	private static final Logger LOGGER = Logger.getLogger(Util.class.getName());
  
  	/**
  	 * Crea una URI a partir de un nombre de fichero local o una URL.
  	 * 
  	 * @param file
  	 *            Nombre del fichero local o URL
  	 * @return URI (<code>file://</code>) del fichero local o URL
  	 * @throws URISyntaxException
  	 *             Si no se puede crear una URI soportada a partir de la cadena de
  	 *             entrada
  	 */
  	public static URI createURI(final String file) throws URISyntaxException {
  
  		if (file == null || file.isEmpty()) {
  			throw new IllegalArgumentException("No se puede crear una URI a partir de un nulo");
  		}
  
  		String filename = file.trim();
  
  		if (filename.isEmpty()) {
  			throw new IllegalArgumentException("La URI no puede ser una cadena vacia");
  		}
  
  		// Cambiamos los caracteres Windows
  		filename = filename.replace('\\', '/');
  
  		// Realizamos los cambios necesarios para proteger los caracteres no
  		// seguros de la URL
  		filename = filename.replace(" ", "%20").replace("<", "%3C").replace(">", "%3E").replace("\"", "%22")
  				.replace("{", "%7B").replace("}", "%7D").replace("|", "%7C").replace("^", "%5E").replace("[", "%5B")
  				.replace("]", "%5D").replace("`", "%60");
  
  		URI uri = new URI(filename);
  
  		// Comprobamos si es un esquema soportado
  		String scheme = uri.getScheme();
  		for (String element : SUPPORTED_URI_SCHEMES) {
  			if (element.equals(scheme)) {
  				return uri;
  			}
  		}
  
  		// Si el esquema es nulo, aun puede ser un nombre de fichero valido
  		// El caracter '#' debe protegerse en rutas locales
  		if (scheme == null) {
  			filename = filename.replace("#", "%23"); //$NON-NLS-1$ //$NON-NLS-2$
  			return createURI("file://" + filename); //$NON-NLS-1$
  		}
  
  		// Miramos si el esquema es una letra, en cuyo caso seguro que es una
  		// unidad de Windows ("C:", "D:", etc.), y le anado el file://
  		// El caracter '#' debe protegerse en rutas locales
  		if (scheme.length() == 1 && Character.isLetter((char) scheme.getBytes()[0])) {
  			filename = filename.replace("#", "%23"); //$NON-NLS-1$ //$NON-NLS-2$
  			return createURI("file://" + filename); //$NON-NLS-1$
  		}
  
  		throw new URISyntaxException(filename, "Tipo de URI no soportado"); //$NON-NLS-1$
  
  	}
  
  	/**
  	 * Obtiene el flujo de entrada de un fichero (para su lectura) a partir de su
  	 * URI.
  	 * 
  	 * @param uri
  	 *            URI del fichero a leer
  	 * @return Flujo de entrada hacia el contenido del fichero
  	 * @throws IOException
  	 *             Cuando no se ha podido abrir el fichero de datos.
  	 */
  	public static InputStream loadFile(URI uri) throws IOException {
  		if (uri == null) {
  			throw new IllegalArgumentException("Se ha pedido el contenido de una URI nula");
  		}
  
  		if (uri.getScheme().equals("file")) {
  			// Es un fichero en disco. Las URL de Java no soportan file://, con
  			// lo que hay que diferenciarlo a mano
  
  			// Retiramos el "file://" de la uri
  			String path = uri.getSchemeSpecificPart();
  			if (path.startsWith("//")) {
  				path = path.substring(2);
  			}
  			return new FileInputStream(new File(path));
  		}
  
  		// Es una URL
  		InputStream tmpStream = new BufferedInputStream(uri.toURL().openStream());
  
  		// Las firmas via URL fallan en la descarga por temas de Sun, asi que
  		// descargamos primero
  		// y devolvemos un Stream contra un array de bytes
  		byte[] tmpBuffer = getDataFromInputStream(tmpStream);
  
  		return new java.io.ByteArrayInputStream(tmpBuffer);
  	}
  
  	/**
  	 * Lee un flujo de datos de entrada y los recupera en forma de array de bytes.
  	 * Este m&eacute;todo consume pero no cierra el flujo de datos de entrada.
  	 * 
  	 * @param input
  	 *            Flujo de donde se toman los datos.
  	 * @return Los datos obtenidos del flujo.
  	 * @throws IOException
  	 *             Cuando ocurre un problema durante la lectura
  	 */
  	public static byte[] getDataFromInputStream(InputStream input) throws IOException {
  		if (input == null) {
  			return new byte[0];
  		}
  		int nBytes;
  		final byte[] buffer = new byte[BUFFER_SIZE];
  		final ByteArrayOutputStream baos = new ByteArrayOutputStream();
  		while ((nBytes = input.read(buffer)) != -1) {
  			baos.write(buffer, 0, nBytes);
  		}
  		return baos.toByteArray();
  	}
  
  	/**
  	 * Obtiene el nombre com&uacute;n (Common Name, CN) del titular de un
  	 * certificado X.509. Si no se encuentra el CN, se devuelve la unidad
  	 * organizativa (Organization Unit, OU).
  	 * 
  	 * @param c
  	 *            Certificado X.509 del cual queremos obtener el nombre com&uacute;n
  	 * @return Nombre com&uacute;n (Common Name, CN) del titular de un certificado
  	 *         X.509
  	 */
  	public static String getCN(final X509Certificate c) {
  		if (c == null) {
  			return null;
  		}
  		return getCN(c.getSubjectX500Principal().toString());
  	}
  
  	/**
  	 * Obtiene el nombre com&uacute;n (Common Name, CN) de un <i>Principal</i>
  	 * X.400. Si no se encuentra el CN, se devuelve la unidad organizativa
  	 * (Organization Unit, OU).
  	 * 
  	 * @param principal
  	 *            <i>Principal</i> del cual queremos obtener el nombre com&uacute;n
  	 * @return Nombre com&uacute;n (Common Name, CN) de un <i>Principal</i> X.400
  	 */
  	public static String getCN(final String principal) {
  		if (principal == null) {
  			return null;
  		}
  
  		String rdn = getRDNvalueFromLdapName("cn", principal); //$NON-NLS-1$
  		if (rdn == null) {
  			rdn = getRDNvalueFromLdapName("ou", principal); //$NON-NLS-1$
  		}
  
  		if (rdn != null) {
  			return rdn;
  		}
  
  		final int i = principal.indexOf('=');
  		if (i != -1) {
  			LOGGER.warning(
  					"No se ha podido obtener el Common Name ni la Organizational Unit, se devolvera el fragmento mas significativo"); //$NON-NLS-1$
  			return getRDNvalueFromLdapName(principal.substring(0, i), principal);
  		}
  
  		LOGGER.warning("Principal no valido, se devolvera la entrada"); //$NON-NLS-1$
  		return principal;
  	}
  
  	/**
  	 * Recupera el valor de un RDN (<i>Relative Distinguished Name</i>) de un
  	 * principal. El valor de retorno no incluye el nombre del RDN, el igual, ni las
  	 * posibles comillas que envuelvan el valor. La funci&oacute;n no es sensible a
  	 * la capitalizaci&oacute;n del RDN. Si no se encuentra, se devuelve
  	 * {@code null}.
  	 * 
  	 * @param rdn
  	 *            RDN que deseamos encontrar.
  	 * @param principal
  	 *            Principal del que extraer el RDN (seg&uacute;n la
  	 *            <a href="http://www.ietf.org/rfc/rfc4514.txt">RFC 4514</a>).
  	 * @return Valor del RDN indicado o {@code null} si no se encuentra.
  	 */
  	public static String getRDNvalueFromLdapName(final String rdn, final String principal) {
  
  		int offset1 = 0;
  		while ((offset1 = principal.toLowerCase(Locale.US).indexOf(rdn.toLowerCase(), offset1)) != -1) {
  
  			if (offset1 > 0 && principal.charAt(offset1 - 1) != ',' && principal.charAt(offset1 - 1) != ' ') {
  				offset1++;
  				continue;
  			}
  
  			offset1 += rdn.length();
  			while (offset1 < principal.length() && principal.charAt(offset1) == ' ') {
  				offset1++;
  			}
  
  			if (offset1 >= principal.length()) {
  				return null;
  			}
  
  			if (principal.charAt(offset1) != '=') {
  				continue;
  			}
  
  			offset1++;
  			while (offset1 < principal.length() && principal.charAt(offset1) == ' ') {
  				offset1++;
  			}
  
  			if (offset1 >= principal.length()) {
  				return ""; //$NON-NLS-1$
  			}
  
  			int offset2;
  			if (principal.charAt(offset1) == ',') {
  				return ""; //$NON-NLS-1$
  			} else if (principal.charAt(offset1) == '"') {
  				offset1++;
  				if (offset1 >= principal.length()) {
  					return ""; //$NON-NLS-1$
  				}
  
  				offset2 = principal.indexOf('"', offset1);
  				if (offset2 == offset1) {
  					return ""; //$NON-NLS-1$
  				} else if (offset2 != -1) {
  					return principal.substring(offset1, offset2);
  				} else {
  					return principal.substring(offset1);
  				}
  			} else {
  				offset2 = principal.indexOf(',', offset1);
  				if (offset2 != -1) {
  					return principal.substring(offset1, offset2).trim();
  				}
  				return principal.substring(offset1).trim();
  			}
  		}
  
  		return null;
  	}
  
  	/**
  	 * Equivalencias de hexadecimal a texto por la posici&oacute;n del vector. Para
  	 * ser usado en <code>hexify()</code>
  	 */
  	private static final char[] HEX_CHARS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E',
  			'F' };
  
  	/**
  	 * Convierte un vector de octetos en una cadena de caracteres que contiene la
  	 * representaci&oacute;n hexadecimal. Copiado directamente de
  	 * opencard.core.util.HexString
  	 * 
  	 * @param abyte0
  	 *            Vector de octetos que deseamos representar textualmente
  	 * @param separator
  	 *            Indica si han o no de separarse los octetos con un gui&oacute;n y
  	 *            en l&iacute;neas de 16
  	 * @return Representaci&oacute;n textual del vector de octetos de entrada
  	 */
  	public static String hexify(final byte abyte0[], final boolean separator) {
  		if (abyte0 == null) {
  			return "null"; //$NON-NLS-1$
  		}
  
  		final StringBuffer stringbuffer = new StringBuffer(256);
  		int i = 0;
  		for (int j = 0; j < abyte0.length; j++) {
  			if (separator && i > 0) {
  				stringbuffer.append('-');
  			}
  			stringbuffer.append(HEX_CHARS[abyte0[j] >> 4 & 0xf]);
  			stringbuffer.append(HEX_CHARS[abyte0[j] & 0xf]);
  			++i;
  			if (i == 16) {
  				if (separator && j < abyte0.length - 1) {
  					stringbuffer.append('\n');
  				}
  				i = 0;
  			}
  		}
  		return stringbuffer.toString();
  	}
  
  	/**
  	 * Convierte un vector de octetos en una cadena de caracteres que contiene la
  	 * representaci&oacute;n hexadecimal. Copiado directamente de
  	 * opencard.core.util.HexString
  	 * 
  	 * @param abyte0
  	 *            Vector de octetos que deseamos representar textualmente
  	 * @param separator
  	 *            Indica si han o no de separarse los octetos con un gui&oacute;n y
  	 *            en l&iacute;neas de 16
  	 * @return Representaci&oacute;n textual del vector de octetos de entrada
  	 */
  	public static String hexify(final byte abyte0[], final String separator) {
  		if (abyte0 == null) {
  			return "null"; //$NON-NLS-1$
  		}
  
  		final StringBuffer stringbuffer = new StringBuffer(256);
  		for (int j = 0; j < abyte0.length; j++) {
  			if (separator != null && j > 0) {
  				stringbuffer.append(separator);
  			}
  			stringbuffer.append(HEX_CHARS[abyte0[j] >> 4 & 0xf]);
  			stringbuffer.append(HEX_CHARS[abyte0[j] & 0xf]);
  		}
  		return stringbuffer.toString();
  	}
  
  	/**
  	 * Carga una librer&iacute;a nativa del sistema.
  	 * 
  	 * @param path
  	 *            Ruta a la libreria de sistema.
  	 * @throws IOException
  	 *             Si ocurre alg&uacute;n problema durante la carga
  	 */
  	public static void loadNativeLibrary(final String path) throws IOException {
  		if (path == null) {
  			LOGGER.warning("No se puede cargar una biblioteca nula"); //$NON-NLS-1$
  			return;
  		}
  		final int pos = path.lastIndexOf('.');
  		final File file = new File(path);
  		final File tempLibrary = File.createTempFile(
  				pos < 1 ? file.getName() : file.getName().substring(0, file.getName().indexOf('.')),
  				pos < 1 || pos == path.length() - 1 ? null : path.substring(pos));
  
  		// Copiamos el fichero
  		copyFile(file, tempLibrary);
  
  		// Pedimos borrar los temporales cuando se cierre la JVM
  		if (tempLibrary != null) {
  			tempLibrary.deleteOnExit();
  		}
  
  		LOGGER.info("Cargamos " + (tempLibrary == null ? path : tempLibrary.getAbsolutePath())); //$NON-NLS-1$
  		System.load(tempLibrary != null ? tempLibrary.getAbsolutePath() : path);
  
  	}
  
  	/**
  	 * Copia un fichero.
  	 * 
  	 * @param source
  	 *            Fichero origen con el contenido que queremos copiar.
  	 * @param dest
  	 *            Fichero destino de los datos.
  	 * @throws IOException
  	 *             SI ocurre algun problema durante la copia
  	 */
  	public static void copyFile(final File source, final File dest) throws IOException {
  		if (source == null || dest == null) {
  			throw new IllegalArgumentException("Ni origen ni destino de la copia pueden ser nulos"); //$NON-NLS-1$
  		}
  
  		final FileInputStream is = new FileInputStream(source);
  		final FileOutputStream os = new FileOutputStream(dest);
  