PageSourceSearch

https://josemmo.io/facturae-web/js/miniapplet.js

js josemmo.io collected 2026-09-25 22:36:18 UTC 134,895 bytes, 3,366 lines download raw bytes

1
2if (document.all && !window.setTimeout.isPolyfill) {
3	var __nativeST__ = window.setTimeout;
4	window.setTimeout = function (vCallback, nDelay /*, argumentToPass1, argumentToPass2, etc. */) {
5		var aArgs = Array.prototype.slice.call(arguments, 2);
6		return __nativeST__(vCallback instanceof Function ? function () {
7			vCallback.apply(null, aArgs);
8		} : vCallback, nDelay);
9	};
10	window.setTimeout.isPolyfill = true;
11}
12
13var originalXMLHttpRequest = window.XMLHttpRequest;
14
15var MiniApplet = ( function ( window, undefined ) {
16
17		var VERSION = "1.5";
18
19		var JAR_NAME = 'miniapplet-full_1_5.jar';
20
21		var JAVA_ARGUMENTS = '-Xms512M -Xmx512M ';
22
23		var SYSTEM_PROPERTIES = null;
24
25		var clienteFirma = null;
26
27		var codeBase = null;
28
29		var storageServletAddress = null;
30
31		var retrieverServletAddress = null;
32
33		var clientType = null;
34
35		var severeTimeDelay = false;
36
37		var selectedLocale = null;
38
39		var LOCALIZED_STRINGS = new Array();
40		LOCALIZED_STRINGS["es_ES"] = {
41				checktime_warn: "Se ha detectado un desfase horario entre su sistema y el servidor. Se recomienda que se corrija antes de pulsar Aceptar para continuar.",
42				checktime_err: "Se ha detectado un desfase horario entre su sistema y el servidor. Debe corregir la hora de su sistema antes de continuar.",
43				checktime_local_time: "Hora de su sistema",
44				checktime_server_time: "Hora del servidor"
45		};
46		LOCALIZED_STRINGS["gl_ES"] = {
47				checktime_warn: "Destectouse un desfase horario entre o seu sistema e o servidor. Recoméndase corrixilo antes de pulsar Aceptar para continuar.",
48				checktime_err: "Destectouse un desfase horario entre o seu sistema e o servidor. Debe corrixir a hora do seu sistema antes de continuar.",
49				checktime_local_time: "Hora do seu sistema",
50				checktime_server_time: "Hora do servidor"
51		};
52
53		var DEFAULT_LOCALE = LOCALIZED_STRINGS["es_ES"];
54
55		var currentLocale = DEFAULT_LOCALE;
56
57		var defaultKeyStore = null;
58
59		/* ------------------------------------------------ */
60		/* Constantes para la operacion interna del Cliente */
61		/* ------------------------------------------------ */
62
63		/* Tamano del buffer con el que se pasa informacion al applet */
64		var BUFFER_SIZE = 2 * 1024 * 1024;
65
66		/* Cadena que determina el fin de una respuesta */
67		var EOF = "%%EOF%%";
68
69		/* Identifica que se utilizara el MiniApplet. */
70		var TYPE_APPLET = "APPLET";
71
72		/* Identifica que se utilizara una aplicacion nativa de firma. */
73		var TYPE_JAVASCRIPT_WEB_SERVICE = "JAVASCRIPT_WEB_SERVICE";
74		var TYPE_JAVASCRIPT_SOCKET = "JAVASCRIPT_SOCKET";
75
76		/* --------------------------------- */
77		/* Constantes publicas		         */
78		/* --------------------------------- */
79
80		/* Almacenes de certificados */
81
82		var KEYSTORE_WINDOWS = "WINDOWS";
83
84		var KEYSTORE_APPLE = "APPLE";
85
86		var KEYSTORE_PKCS12 = "PKCS12";
87
88		var KEYSTORE_PKCS11 = "PKCS11";
89
90		var KEYSTORE_MOZILLA = "MOZ_UNI";
91
92		var KEYSTORE_SHARED_NSS = "SHARED_NSS";
93
94		var KEYSTORE_JAVA = "JAVA";
95
96		var KEYSTORE_JCEKS = "JCEKS";
97
98		var KEYSTORE_JAVACE = "JAVACE";
99
100		var KEYSTORE_DNIE = "DNIEJAVA";
101
102		/* Valores para la configuracion de la comprobacion de tiempo */
103
104		var CHECKTIME_NO = "CT_NO";
105
106		var CHECKTIME_RECOMMENDED = "CT_RECOMMENDED";
107
108		var CHECKTIME_OBLIGATORY = "CT_OBLIGATORY";
109
110		// Tiempo de espera entre los intentos de conexion con autofirma.
111		var AUTOFIRMA_LAUNCHING_TIME = 2000;
112		
113		// Reintentos de conexion totales para detectar que esta instalado AutoFirma
114		var AUTOFIRMA_CONNECTION_RETRIES = 10;
115
116		// Variable que se puede configurar para forzar el uso del modo de comunicacion por servidor intermedio
117		// entre la pagina web y AutoFirma
118		var forceWSMode = false;
119
120		/* ------------------------------------ */
121		/* Funciones de comprobacion de entorno */
122		/* ------------------------------------ */
123
124		/**
125		 * Determina con un boolean si nuestro cliente es Android
126		 */
127		function isAndroid() {
128			return navigator.userAgent.toUpperCase().indexOf("ANDROID") != -1 ||
129				navigator.appVersion.toUpperCase().indexOf("ANDROID") != -1 ||
130				// Para la deteccion de los Kindle Fire
131				navigator.userAgent.toUpperCase().indexOf("SILK/") != -1 ||
132				navigator.userAgent.toUpperCase().indexOf("KFJWI") != -1 ||
133				navigator.userAgent.toUpperCase().indexOf("KFJWA") != -1 ||
134				navigator.userAgent.toUpperCase().indexOf("KFTT") != -1 ||
135				navigator.userAgent.toUpperCase().indexOf("KFOT") != -1 ||
136				navigator.userAgent.toUpperCase().indexOf("KINDLE FIRE") != -1
137				;
138		}
139
140		/**
141		 * Determina con un boolean si nuestro cliente es iOS.
142		 */
143		function isIOS() {
144			return (navigator.userAgent.toUpperCase().indexOf("IPAD") != -1) ||
145			(navigator.userAgent.toUpperCase().indexOf("IPOD") != -1) ||
146			(navigator.userAgent.toUpperCase().indexOf("IPHONE") != -1);
147		}
148
149		/** Determina con un boolean si nos encontramos en Windows 8/8.1 */
150		function isWindows8() {
151			return navigator.userAgent.indexOf("Windows NT 6.2") != -1 ||	/* Windows 8 */
152				navigator.userAgent.indexOf("Windows NT 6.3") != -1;		/* Windows 8.1 */
153		}
154
155		/** Determina con un boolean si nos encontramos en Windows RT */
156		function isWindowsRT() {
157			return isWindows8() && navigator.userAgent.indexOf("ARM;") != -1;
158		}
159
160		/** Determina con un boolean si estamos en Internet Explorer */
161		function isInternetExplorer() {
162			return !!(navigator.userAgent.match(/MSIE/))	/* Internet Explorer 10 o inferior */
163					|| !!(navigator.userAgent.match(/Trident/) && navigator.userAgent.match(/rv:11/)) /* Internet Explorer 11 o superior (Opcion 1) */
164					|| !!navigator.userAgent.match(/Trident.*rv[ :]*11\./); /* Internet Explorer 11 o superior (Opcion 2) */
165		}
166
167		/** Indica si el navegador es Internet Explorer 10 o inferior. */
168		function isOldInternetExplorer() {
169			return !!(navigator.userAgent.match(/MSIE/));
170		}
171		
172		function isFirefoxUAM() {
173		    return navigator.userAgent.indexOf("UAM") > 0;
174		}
175
176		/**
177		 * Determina con un boolean si se accede a la web con Firefox
178		 */
179		function isFirefox(){
180			return navigator.userAgent.toUpperCase().indexOf("FIREFOX") != -1
181		}
182
183		/**
184		 * Determina con un boolean si se accede a la web con Chrome
185		 */
186		function isChrome() {
187			return navigator.userAgent.toUpperCase().indexOf("CHROME") != -1 ||
188				navigator.userAgent.toUpperCase().indexOf("CHROMIUM") != -1;
189		}
190
191        /**
192         * Determina con un boolean si el navegador es Microsoft Edge
193         */
194        function isEdge() {
195        	return !!navigator.userAgent.match(/Edge\/\d+/);
196        }
197
198		/** Indica si el navegador detecta Java. Este valor no es completamente fiable, ya que
199		 * Internet Explorer siempre indica que si esta activado. */
200		function isJavaEnabled() {
201			return navigator.javaEnabled();
202		}
203
204		/** Comprueba si una cadena de texto es una URL (http/https). La alternativa implicaria ser un Base64. */ 
205		function isValidUrl(data) { 
206			return data != null && data.length > "https://".length &&
207				("http:" == data.substr(0, 5) || "https:" == data.substr(0, 6));
208		}
209
210		var downloadSuccessFunction = null;
211		var downloadErrorFunction = null;
212		var downloadTypeData = null;
213		
214		/**
215		 * Realiza la descarga de datos de una URL y, una vez termina, llama al metodo
216		 * successFunction pasandole los datos descargados en base 64, o errorFunction,
217		 * si fallo en la descarga, al que se pasa el error que produjo el problema.
218		 */
219		function downloadRemoteData(url, successFunction, errorFunction) {
220			
221			downloadSuccessFunction = successFunction;
222			downloadErrorFunction = errorFunction;
223			
224			var httpRequest = getHttpRequest();
225			httpRequest.open("GET", url, true);
226			
227			// new browsers (XMLHttpRequest2-compliant)
228			if ('responseType' in httpRequest && !!Uint8Array) {
229				httpRequest.responseType = 'arraybuffer';
230				downloadTypeData = 'arraybuffer';
231			}
232			// old browsers (XMLHttpRequest-compliant)
233			else
234				if ('overrideMimeType' in httpRequest) {
235				httpRequest.overrideMimeType('text\/plain; charset=x-user-defined');
236				downloadTypeData = 'string';
237			}
238			// IE9 (Microsoft.XMLHTTP-compliant)
239			else {
240				httpRequest.setRequestHeader('Accept-Charset', 'x-user-defined');
241				downloadTypeData = 'bytearray';
242			}
243			
244			// shim for onload for old IE
245			if (!('onload' in httpRequest)) {
246				httpRequest.onreadystatechange = function () {
247					if (this.readyState === 4) {
248						this.onload();
249					}
250				};
251			}
252
253			httpRequest.onload = function (evt) {
254				if (httpRequest.readyState == 4 && httpRequest.status == 200) {
255					if (downloadSuccessFunction) {
256
257						// emulating response field for IE9
258						if (!('response' in this)) {
259							this.response = new VBArray(this.responseBody).toArray();
260						}
261
262						var b64;
263						if (downloadTypeData == 'arraybuffer') {
264							b64 = Base64.encodeArrayBuffer(this.response);
265						}
266						else if (downloadTypeData == 'bytearray') { 
267							b64 = Base64.encodeByteArray(this.response);
268						}
269						else if (downloadTypeData == 'string') {
270							b64 = Base64.encode(this.response);
271						}
272						else {
273							this.onerror(new Error("No se conoce el modo usado para la descarga de los datos"));
274						}
275						downloadSuccessFunction(b64);
276					}
277//					else {
278//						console.log("Se termino la descarga de los datos. No se invoca a ninguna funcion.");
279//					}
280				}
281			}
282			httpRequest.onerror = function(e) {
283				if (downloadErrorFunction) {
284					downloadErrorFunction(e);
285				}
286//				else {
287//					console.log("Error en la descarga de los datos. No se invoca a ninguna funcion.");
288//				}
289			}
290			httpRequest.send();
291		}
292
293		function getHttpRequest() {
294            var xmlHttp = null;
295            
296        	/* Si se cargo Ajax, puede haber modificado el funcionamiento de la clase XMLHttpRequest,
297        	 * asi que vemos si el propio Ajax (Sarissa) guarda la implementacion original; si no,
298        	 * usamos la implementacion por defecto que la habremos guardado al principio del script
299        	 * en la variable originalXMLHttpRequest. Es importante que se haya cargado este script
300        	 * antes que el de Ajax o esta opcion no tendra resultado */
301            if (typeof XMLHttpRequest != "undefined") {	// Navegadores actuales
302                if (typeof Sarissa !== 'undefined' && Sarissa.originalXMLHttpRequest) {
303                	xmlHttp = new Sarissa.originalXMLHttpRequest();
304                } else {
305                	xmlHttp = new originalXMLHttpRequest();
306                }
307            } else if (typeof window.ActiveXObject != "undefined") {	// Internet Explorer antiguos
308                try {
309                    xmlHttp = new ActiveXObject("Msxml2.XMLHTTP.4.0");
310                } catch (e) {
311                    try {
312                        xmlHttp = new ActiveXObject("MSXML2.XMLHTTP");
313                    } catch (e) {
314                        try {
315                            xmlHttp = new ActiveXObject("Microsoft.XMLHTTP");
316                        } catch (e) {
317                            xmlHttp = null;
318                        }
319                    }
320                }
321            }
322            return xmlHttp;
323		}
324
325		/** Permite establecer que la comunicacion con AutoFirma sea a traves
326		 * del servidor intermedio. */
327		var setForceWSMode = function (force) {
328			forceWSMode = force;
329		}
330		
331		/** Permite habilitar la comprobacion de la hora local contra la hora del servidor y
332		 * establecer un tiempo maximo permitido y el comportamiento si se supera.
333		 * Parametros:
334		 *  - checkType:	Tipo de comprobacion. Admite los valores CT_NO, CT_RECOMMENDED y CT_OBLIGATORY.
335		 *  - maxMillis:	Tiempo maximo de desfase en milisegundos.
336		 *  - checkURL:		URL contra la que se realizara la peticion para obtener la hora. Si no se
337		 *  				indica, se usara una pagina web aleatoria dentro del propio dominio.
338		 * Cuando el HTML es local, no se realiza ningun tipo de comprobacion.
339		 */
340		var checkTime = function (checkType, maxMillis, checkURL) {
341			try {
342
343				if (checkType == undefined || checkType == null || checkType == CHECKTIME_NO
344						|| maxMillis == undefined || maxMillis == null || maxMillis <= 0) {
345					return;
346				}
347
348				// Si checkURL existe mandamos la peticion ahi, en caso contrario nos inventamos una url.
349				var URL;
350				if (checkURL) {
351					URL = checkURL;
352				}
353				else {
354					URL = document.URL + '/' + Math.random();
355				}
356				// Hacemos una llamada al servidor para conocer su hora
357				var xhr = getHttpRequest(); 
358				xhr.open('GET', URL, false); 
359				xhr.send();
360	
361				// Recogemos la hora local, nada mas obtener la respuesta del servidor
362				var clientDate = new Date();
363
364				// Tomamos la hora a partir de la respuesta del servidor. Si esta es 0, estamos en local
365				var serverDate = new Date(xhr.getResponseHeader("Date"));
366				if (serverDate == null || serverDate.getTime() == 0) {
367					// No hacemos nada si estamos en local 
368					return;
369				}
370	
371				// Evaluamos la desincronizacion 
372				var delay =  Math.abs(clientDate.getTime() - serverDate.getTime());
373				if (delay > maxMillis) {
374					 if (checkType == CHECKTIME_RECOMMENDED) {
375						 alert(currentLocale.checktime_warn +
376								 "\n" + currentLocale.checktime_local_time + ": " + clientDate.toLocaleString() +
377								 "\n" + currentLocale.checktime_server_time + ": " + serverDate.toLocaleString());
378					 }
379					 else if (checkType == CHECKTIME_OBLIGATORY) {
380						 severeTimeDelay = true;
381						 alert(currentLocale.checktime_err +
382								 "\n" + currentLocale.checktime_local_time + ": " + clientDate.toLocaleString() +
383								 "\n" + currentLocale.checktime_server_time + ": " + serverDate.toLocaleString());
384					 }
385				}
386			}
387			catch (e) {
388				// No hacemos nada si estamos en local 
389				//console.log("Error en la obtencion de la hora del servidor: " + e);
390				return;
391			}
392		}
393		/** Carga el MiniApplet. */
394		var cargarMiniApplet = function (base, keystore) {
395
396			// Antes que nada, comprobamos que no haya un desfase horario declarado como
397			// grave.
398			if (severeTimeDelay) {
399				return;
400			}
401
402			// Sincronizamos las variables que puedan haberse establecido de forma externa
403			// antes de la llamada al metodo de carga
404			JAVA_ARGUMENTS = MiniApplet.JAVA_ARGUMENTS;
405			SYSTEM_PROPERTIES = MiniApplet.SYSTEM_PROPERTIES;
406
407			// Establecemos cual sera el keystore por defecto 
408			defaultKeyStore = keystore;
409			if (!defaultKeyStore) {
410				defaultKeyStore = getDefaultKeystore();
411			}
412
413			// Configuramos los argumentos para la seleccion de almacen
414			configureKeyStore();
415
416			// Si estamos claramente en un sistema movil o que no permite la ejecucion de Java,
417			// cargamos directamente el Cliente JavaScript
418			if (isAndroid() || isIOS() || isWindowsRT() || isChrome() || isEdge()) {
419				cargarAppAfirma(base, defaultKeyStore);
420				return;
421			}
422
423			// Si estamos en un entorno que permite Java, comprobamos si esta disponible
424			// y en caso de no estarlo, tambien cargamos el Cliente JavaScript.
425			if (!isJavaEnabled()) {
426				cargarAppAfirma(base, defaultKeyStore);
427				return;
428			}
429
430			// Incluso si el navegador informa que hay Java, puede no haberlo (Internet Explorer
431			// siempre dice que hay), asi que cargamos el applet, pero tenemos en cuenta que en
432			// caso de error debemos cargar el cliente JavaScript
433			codeBase = (base != undefined && base != null) ? base : './';
434
435			var attributes = {
436					'id': 'miniApplet',
437					'name': 'MiniApplet @firma (Gobierno de Espa\u00F1a)',
438					'type': 'application/x-java-applet',
439					'width': 1,
440					'height': 1
441			};
442
443			// Los argumentos de java no llegan al propio applet en las pruebas con Java 6 y 7,
444			// asi que (salvo los argumentos de carga) vamos a pasarlos como un parametro mas al
445			// applet para luego establecerlos internamente.
446			var parameters = {
447					'keystore': defaultKeyStore,
448					'userAgent': window.navigator.userAgent,
449					'archive': codeBase + '/' + JAR_NAME,
450					'code': 'es.gob.afirma.miniapplet.MiniAfirmaApplet',
451					'java-vm-args': JAVA_ARGUMENTS,
452					'java_arguments': JAVA_ARGUMENTS,
453					'system_properties': SYSTEM_PROPERTIES,
454					'codebase_lookup': false,
455					'separate_jvm': true,
456					'locale': selectedLocale
457			};
458
459			loadMiniApplet(attributes, parameters);
460
461			if (isFirefox()) {
462				clienteFirma = document.getElementById("miniApplet");
463
464				// Si no esta definido el cliente es porque se ha intentado cargar el applet
465				// y no se ha podido, asi que se usara la aplicacion nativa
466				if (clienteFirma == null) {
467					cargarAppAfirma(codeBase, defaultKeyStore);
468				}
469			}
470			else {
471				clienteFirma = document.getElementById("miniApplet");
472
473				// Si no esta definido el cliente es porque se ha intentado cargar el applet
474				// y no se ha podido, asi que se usara la aplicacion nativa
475				if (clienteFirma == null) {
476					cargarAppAfirma(codeBase, defaultKeyStore);
477				}
478			}
479		}
480
481		/** Obtiene el nombre del almacen que corresponde al presente navegador o, si se debe acceder
482		 * al almacen del sistema, se devuelve null. */
483		function getDefaultKeystore() {
484			if(isFirefox()){
485				return KEYSTORE_MOZILLA;
486			}
487			return null;
488		}
489		
490		/** Establece los parametros de configuracion para la correcta seleccion del almacen
491		 * de claves que se debe cargar. */
492		function configureKeyStore() {
493			if (isFirefoxUAM()) {
494				if (SYSTEM_PROPERTIES == null) {
495					SYSTEM_PROPERTIES = "";
496				}
497				SYSTEM_PROPERTIES += " -Des.gob.afirma.keystores.mozilla.UseEnvironmentVariables=true ";
498			}
499		}
500
501		/** Carga el MiniApplet. */
502		var setKeyStore = function (ksType) {
503			
504			forceLoad();
505			
506			clienteFirma.setKeyStore(ksType != null ? ksType : defaultKeyStore);
507		}
508		
509		var selectCertificate = function (params, successCallback, errorCallback) {
510			
511			forceLoad();
512
513			if (clientType == TYPE_APPLET) {
514				try {
515					var certificate = clienteFirma.selectCertificate(params);
516					if (successCallback == undefined || successCallback == null) {
517						return certificate;
518					}
519					successCallback(certificate);
520				} catch(e) {
521					if (errorCallback == undefined || errorCallback == null) {
522						throw e;
523					}
524					errorCallback(clienteFirma.getErrorType(), clienteFirma.getErrorMessage());
525				}
526			}
527			else {
528				clienteFirma.selectCertificate(params, successCallback, errorCallback);
529			}
530		}
531			
532		var sign = function (dataB64, algorithm, format, params, successCallback, errorCallback) {
533			
534			forceLoad();
535			
536			if (clientType == TYPE_APPLET) {
537				try {
538					setData(dataB64);
539					var certSignaturePair = buildData(clienteFirma.sign(algorithm, format, params));
540					var sepPos = certSignaturePair.indexOf('|');
541					if (successCallback == undefined || successCallback == null) {
542						return certSignaturePair.substring(sepPos + 1);
543					}
544					successCallback(certSignaturePair.substring(sepPos + 1), certSignaturePair.substring(0, sepPos));
545				} catch(e) {
546					if (errorCallback == undefined || errorCallback == null) {
547						throw e;
548					}
549					errorCallback(clienteFirma.getErrorType(), clienteFirma.getErrorMessage());
550				}
551			}
552			else {
553				clienteFirma.sign(dataB64, algorithm, format, params, successCallback, errorCallback);
554			}
555		}
556
557		var coSign = function (signB64, dataB64, algorithm, format, params, successCallback, errorCallback) {
558
559			forceLoad();
560
561			if (clientType == TYPE_APPLET) {
562				try {
563					setData(signB64);
564					var certSignaturePair = buildData(clienteFirma.coSign(dataB64, algorithm, format, params));
565					var sepPos = certSignaturePair.indexOf('|');
566					if (successCallback == undefined || successCallback == null) {
567						return certSignaturePair.substring(sepPos + 1);
568					}
569					successCallback(certSignaturePair.substring(sepPos + 1), certSignaturePair.substring(0, sepPos));
570				} catch(e) {
571					if (errorCallback == undefined || errorCallback == null) {
572						throw e;
573					}
574					errorCallback(clienteFirma.getErrorType(), clienteFirma.getErrorMessage());
575				}
576			}
577			else {
578				clienteFirma.coSign(signB64, dataB64, algorithm, format, params, successCallback, errorCallback);
579			}
580		}
581
582		
583		var counterSign = function (signB64, algorithm, format, params, successCallback, errorCallback) {
584
585			forceLoad();
586
587			if (clientType == TYPE_APPLET) {
588				try {
589					setData(signB64);
590					var certSignaturePair = buildData(clienteFirma.counterSign(algorithm, format, params));
591					var sepPos = certSignaturePair.indexOf('|');
592					if (successCallback == undefined || successCallback == null) {
593						return certSignaturePair.substring(sepPos + 1);
594					}
595					successCallback(certSignaturePair.substring(sepPos + 1), certSignaturePair.substring(0, sepPos));
596				} catch(e) {
597					if (errorCallback == undefined || errorCallback == null) {
598						throw e;
599					}
600					errorCallback(clienteFirma.getErrorType(), clienteFirma.getErrorMessage());
601				}
602			}
603			else {
604				clienteFirma.counterSign(signB64, algorithm, format, params, successCallback, errorCallback);
605			}
606		}
607
608		var signAndSaveToFile = function (operationId, dataB64, algorithm, format, params, outputFileName, successCallback, errorCallback) {
609			
610			forceLoad();
611			
612			if (clientType == TYPE_APPLET) {
613				try {
614					setData(dataB64);
615					var certSignaturePair = buildData(clienteFirma.signAndSaveToFile(operationId, algorithm, format, params, outputFileName));
616					var sepPos = certSignaturePair.indexOf('|');
617					if (successCallback == undefined || successCallback == null) {
618						return certSignaturePair.substring(sepPos + 1);
619					}
620					successCallback(certSignaturePair.substring(sepPos + 1), certSignaturePair.substring(0, sepPos));
621				} catch(e) {
622					if (errorCallback == undefined || errorCallback == null) {
623						throw e;
624					}
625					errorCallback(clienteFirma.getErrorType(), clienteFirma.getErrorMessage());
626				}
627			}
628			else {
629				clienteFirma.signAndSaveToFile(operationId, dataB64, algorithm, format, params, outputFileName, successCallback, errorCallback);
630			}
631		}
632
633		var signBatch = function (batchB64, batchPreSignerUrl, batchPostSignerUrl, params, successCallback, errorCallback) {
634
635			forceLoad();
636
637			if (clientType == TYPE_APPLET) {
638				try {
639					if (successCallback == undefined || successCallback == null) {
640						return clienteFirma.signBatch(batchB64, batchPreSignerUrl, batchPostSignerUrl, params);
641					}
642					successCallback(clienteFirma.signBatch(batchB64, batchPreSignerUrl, batchPostSignerUrl, params));
643				}
644				catch (e) {
645					if (errorCallback == undefined || errorCallback == null) {
646						throw e;
647					}
648					errorCallback(clienteFirma.getErrorType(), clienteFirma.getErrorMessage());
649					return;
650				}
651			}
652			else {
653				clienteFirma.signBatch(batchB64, batchPreSignerUrl, batchPostSignerUrl, params, successCallback, errorCallback);
654			}
655		}
656		
657		var getBase64FromText = function (plainText, charset) {
658			forceLoad();
659			return clienteFirma.getBase64FromText(plainText, charset);
660		}
661
662		var getTextFromBase64 = function (dataB64, charset) {
663			forceLoad();
664			return clienteFirma.getTextFromBase64(dataB64, charset);
665		}
666
667		var saveDataToFile = function (dataB64, title, fileName, extension, description, successCallback, errorCallback) {
668			forceLoad();
669			if (clientType == TYPE_APPLET) {
670				setData(dataB64);
671				return clienteFirma.saveDataToFile(title, fileName, extension, description);
672			}
673			else {
674				clienteFirma.saveDataToFile(dataB64, title, fileName, extension, description, successCallback, errorCallback);
675			}
676		}
677
678		var getFileNameContentBase64 = function (title, extensions, description, filePath) {
679			forceLoad();
680			return buildData(clienteFirma.getFileNameContentBase64(title, extensions, description, filePath));
681		}
682
683		var getMultiFileNameContentBase64 = function (title, extensions, description, filePath) {
684			forceLoad();
685			return buildData(clienteFirma.getMultiFileNameContentBase64(title, extensions, description, filePath));
686		}
687
688		var echo = function () {
689			forceLoad();
690			return clienteFirma.echo();
691		}
692
693		var setStickySignatory = function (sticky) {
694			forceLoad();
695			return clienteFirma.setStickySignatory(sticky);
696		}
697
698		var setLocale = function (locale) {
699			selectedLocale = locale;
700			currentLocale = (locale == null || LOCALIZED_STRINGS[locale] == null ? DEFAULT_LOCALE : LOCALIZED_STRINGS[locale]); 
701		}
702
703		var getErrorMessage = function () {
704			forceLoad();
705			return clienteFirma.getErrorMessage();
706		}
707
708		var getErrorType = function () {
709			forceLoad();
710			return clienteFirma.getErrorType();
711		}
712
713		var getCurrentLog = function () {
714			forceLoad();
715			return	" === JAVASCRIPT INFORMATION === " +
716					"\nnavigator.appCodeName: " + navigator.appCodeName +
717					"\nnavigator.appName: " +  navigator.appName +
718					"\nnavigator.appVersion: " + navigator.appVersion +
719					"\nnavigator.platform: " + navigator.platform +
720					"\nnavigator.userAgent: " + navigator.userAgent+
721					"\nnavigator.javaEnabled(): " + navigator.javaEnabled() +
722					"\nscreen.width: " + (window.screen ? screen.width : 0) +
723					"\nscreen.height: " + (window.screen ? screen.height : 0) +
724					"\n\n   === CLIENTE LOG === \n" + 
725					clienteFirma.getCurrentLog();
726		}
727
728		var setServlets = function (storageServlet,  retrieverServlet) {
729			
730			storageServletAddress = storageServlet;
731			retrieverServletAddress = retrieverServlet;
732
733			if (clienteFirma && clienteFirma.setServlets) {
734				clienteFirma.setServlets(storageServlet,  retrieverServlet);
735			}
736		}
737
738		/*************************************************************
739		 *  FUNCIONES PARA EL DESPLIEGUE DEL APPLET					 *
740		 **************************************************************/
741
742		function loadMiniApplet(attributes, parameters) {
743			// Internet Explorer se carga mediante un
744			// elemento <object>. El resto con un <embed>.
745			if (isInternetExplorer()) {
746				
747				var appletTag = "<object classid='clsid:8AD9C840-044E-11D1-B3E9-00805F499D93' width='" + attributes["width"] + "' height='" + attributes["height"] + "' id='" + attributes["id"] + "'>";
748
749				if (attributes != undefined && attributes != null) {
750					for (var attribute in attributes) {
751						appletTag += "<param name='" + attribute + "' value='" + attributes[attribute] + "' />";
752					}
753				}
754
755				if (parameters != undefined && parameters != null) {
756					for (var parameter in parameters) {
757						appletTag += "<param name='" + parameter + "' value='" + parameters[parameter] + "' />";
758					}
759				}
760
761				appletTag += "</object>";
762
763				// Al agregar estos nodos con append() no se carga automaticamente el applet en IE10 e inferiores, asi que
764				// hay que usar document.write() o innerHTML. Para asegurarnos de no pisar HTML previo, crearemos un <div>
765				// en la pagina, lo recogeremos e insertaremos dentro suyo el codigo del applet.
766				var divElem = document.createElement("div");
767				var idAtt = document.createAttribute("id");
768				idAtt.value = 'divAfirmaApplet';
769				divElem.setAttributeNode(idAtt);
770
771				document.body.appendChild(divElem);
772				
773				document.getElementById("divAfirmaApplet").innerHTML = appletTag;
774			}
775			else {
776				var embed = document.createElement("embed");
777
778				if (attributes != undefined && attributes != null) {
779					for (var attribute in attributes) {
780						var att = document.createAttribute(attribute);
781						att.value = attributes[attribute];
782						try {
783							embed.setAttributeNode(att);
784						}
785						catch (e) {
786							// Probamos este como alternativa en caso de error. Caso detectado en:
787							// - IE10 sin modo de compabilidad con el Document Mode de IE7.
788							// - Firefox en Mac OS X
789							// Este intento no soluciona el error, pero evita que se propague 
790							embed.setAttribute(attribute, attributes[attribute]);
791						}
792					}
793				}
794
795				if (parameters != undefined && parameters != null) {
796					for (var parameter in parameters) {
797						var att = document.createAttribute(parameter);
798						att.value = parameters[parameter];
799						embed.setAttributeNode(att);
800					}
801				}
802
803				document.body.appendChild(embed);
804			}
805		}
806		
807		/**
808		 * Establece los datos que debera procesar el applet MiniApplet. 
809		 */
810		function forceLoad() {
811
812			// Antes que nada, comprobamos que no haya un desfase horario declarado como
813			// grave.
814			if (severeTimeDelay) {
815				return;
816			}
817			if (clientType == null || clientType == TYPE_JAVASCRIPT_WEB_SERVICE || clientType == TYPE_JAVASCRIPT_SOCKET ) {
818				
819				var tempCliente = document.getElementById("miniApplet");
820				var appletLoaded;
821				try {
822					appletLoaded = tempCliente != null && tempCliente.echo() != "Cliente JavaScript"; 
823				}
824				catch (e) {
825					appletLoaded = false;
826				}
827				if (appletLoaded) {
828					clienteFirma = tempCliente;
829					clientType = TYPE_APPLET;
830				}
831				else if (clientType == null) {
832					cargarAppAfirma(codeBase, defaultKeyStore);	
833				}
834				setServlets(storageServletAddress, retrieverServletAddress);
835			}
836		}
837
838		/**
839		 * Establece los datos que debera procesar el applet MiniApplet. Los datos se le
840		 * pasan al applet en fragmentos porque el JavaPlugin no permite recibir o enviar
841		 * mas de 4Mb de datos de una sola vez desde JavaScript.
842		 */
843		function setData(dataB64) {
844
845			if (dataB64 == null) {
846				return;
847			}
848			else if (dataB64.length <= BUFFER_SIZE) {
849				clienteFirma.addData(dataB64);	
850			}
851			else {
852				clienteFirma.addData(dataB64.substring(0, BUFFER_SIZE));
853				setData(dataB64.substring(BUFFER_SIZE));
854			}
855		}
856
857		/**
858		 * Construye el resultado de una funcion a partir de los trozos en la que esta los divide. 
859		 */
860		function buildData(dataB64) {
861			var buffer = dataB64;
862			var chunk = clienteFirma.getRemainingData();
863			while(chunk != EOF) {
864				buffer += chunk;
865				chunk = clienteFirma.getRemainingData();
866			}
867			return buffer;
868		}
869
870		/**************************************************************
871		 **************************************************************
872		 **************************************************************
873		 **************************************************************
874		 *  FUNCIONES DEL CLIENTE JAVASCRIPT						  *
875		 **************************************************************
876		 **************************************************************
877		 **************************************************************
878		 **************************************************************/
879
880		/**
881		 * Establece el objeto que simula ser el Applet de firma en sistemas en los que no se
882		 * soportan los applets. Se usara comunicacion mediante socket cuando:
883		 *  - El sistema operativo no sea Android.
884		 *  - El sistema operativo no sea iOS.
885		 *  - El navegador web no sea Intenet Explorer 10 o inferior (o 11 con modo de compatibilidad)
886		 *  - No se fuerce el uso del servidor intermedio.
887		 * En caso contrario, la comunicacion se realizara mediante un servidor intermedio.
888		 */
889		function cargarAppAfirma(clientAddress, keystore) {
890			
891			if (!keystore) {
892				keystore = getDefaultKeystore();
893			}
894			
895			if (!isIOS() && !isAndroid() && !isOldInternetExplorer() && !isEdge() && !forceWSMode){
896				clienteFirma = new AppAfirmaJSSocket(clientAddress, window, undefined);
897				clienteFirma.setKeyStore(keystore);
898				clientType = TYPE_JAVASCRIPT_SOCKET;
899			}
900			else {
901				clienteFirma = new AppAfirmaJSWebService(clientAddress, window, undefined);
902				clienteFirma.setKeyStore(keystore);
903				clientType = TYPE_JAVASCRIPT_WEB_SERVICE;
904			}
905
906		}
907
908		var AppAfirmaJSSocket = ( function (clientAddress, window, undefined) {
909
910			var UnsupportedOperationException = "java.lang.UnsupportedOperationException";
911
912			/**
913			 *  Atributos para la configuracion del objeto sustituto del applet Java de firma
914			 */
915			var errorMessage = '';
916			var errorType = '';
917
918			/** Puerto actual */
919			var port = "";
920			
921			var idSession;
922			
923			var PROTOCOL_VERSION = 1;
924			
925			/* Tiempo de retardo para peticiones */
926			var WAITING_TIME = 500;
927			
928			var URL_REQUEST = "https://127.0.0.1:";
929			
930			/* Respuesta del socket a la peticion realizada */
931			var totalResponseRequest = "";
932			
933			/* Numero de fragmentos recibidos del socket */
934			var recibidos = 0;
935			
936			/* Variable de control para la operacion seleccion de certificado */ 
937			var isSelectCertOperation = false;
938			
939			/* Variable de control para la operacion guardar */
940			var isSaveOperation = false;
941			
942			/* Variable de control para la operacion de ejecutar operacion criptografica y guardar */
943			var isOpAndSaveOperation = false;
944			
945			/* Variable de control para la operacion batch */
946			var isBatchOperation = false;
947			
948			/* URL de la peticion HTTPS */
949			var urlHttpRequest = "";
950			
951			/* Maxima longitud permitida para una URL. Si la url se excede se divide
952			 * la peticion en fragmentos. Con Internet Explorer y Firefox a partir de
953			 * la version 49 se producen corrupciones de datos con el valor original
954			 * de 1Mb, asi que se reduce este a un valor seguro para ese navegador. */
955			var URL_MAX_SIZE = isInternetExplorer() ? 12000 : isFirefox() ? 458752 : 1048576;
956			
957			/* Indica si se ha establecido la conexion o no */
958			var connection = false;
959		
960			/* Dominio desde el que se realiza la llamada al servicio */
961			var baseUri = clientAddress;
962
963			/* Almacen de claves cargado por defecto */
964			var defaultKeyStore = null;
965
966			/* Funcion callback que se lanza al obtener un resultado */ 
967			var successCallback = null;
968			
969			/* Funcion error callback */
970			var errorCallback = null;
971			
972			/* Mayor entero */
973			var MAX_NUMBER = 2147483648;
974
975			/* Caracteres validos para los ID de sesion */
976			var VALID_CHARS_TO_ID = "1234567890abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
977
978			/* Genera un identificador de sesion. */
979			function generateNewIdSession () {
980				var ID_LENGTH = 20;
981				var random = "";
982				var randomInts;
983				if (typeof window.crypto != "undefined" && typeof window.crypto.getRandomValues != "undefined") {
984					randomInts = new Uint32Array(ID_LENGTH);
985					window.crypto.getRandomValues(randomInts);
986				}
987				else {
988					randomInts = new Array(ID_LENGTH);
989					for (var i = 0; i < ID_LENGTH; i++) {
990						randomInts[i] = rnd() * MAX_NUMBER;
991					}
992				}
993
994				for (var i = 0; i < ID_LENGTH; i++) {
995					random += VALID_CHARS_TO_ID.charAt(Math.floor(randomInts[i] % VALID_CHARS_TO_ID.length));
996				}
997
998				return random;
999			}
1000			
1001			/**
1002			 * Genera numeros aleatorios con una distribucion homogenea.
1003			 */
1004			var seed;
1005			function rnd () {
1006				if (seed == undefined) {
1007					seed = new Date().getMilliseconds() * 1000 * Math.random();
1008				}
1009			    seed = (seed * 9301 + 49297) % 233280;
1010			    return seed / 233280;
1011			}
1012			
1013			/**
1014			 * Establece el almacen de certificados de que se debe utilizar por defecto.
1015			 */
1016			function setKeyStore (keystore) {
1017				defaultKeyStore = keystore;
1018			}
1019			
1020			/**
1021			 * Inicia el proceso de seleccion de certificado.
1022			 */
1023			function selectCertificate (extraParams, successCallbackFunction, errorCallbackFunction) {
1024				successCallback = successCallbackFunction;
1025				errorCallback = errorCallbackFunction;
1026				selectCertByService(extraParams);
1027			}
1028
1029			function selectCertByService (extraParams) {
1030				
1031				var data = new Object();
1032				data.op = generateDataKeyValue ("op", "selectcert");
1033				data.properties = generateDataKeyValue ("properties", extraParams != null ? Base64.encode(extraParams) : null);
1034				data.keystore = generateDataKeyValue ("keystore", defaultKeyStore != null ? defaultKeyStore : null);
1035				data.ksb64 = generateDataKeyValue ("ksb64", defaultKeyStore != null ? Base64.encode(defaultKeyStore) : null);
1036				
1037				execAppIntent(buildUrl(data));
1038			}
1039			
1040			/**
1041			 * Inicia el proceso de firma electronica.
1042			 * Implementada en el applet Java de firma
1043			 */
1044			function sign (dataB64, algorithm, format, extraParams, successCallbackFunction, errorCallbackFunction) {
1045				successCallback = successCallbackFunction;
1046				errorCallback = errorCallbackFunction;
1047				signByService("sign", dataB64, algorithm, format, extraParams);
1048			}
1049
1050			/**
1051			 * Inicia el proceso de cofirma de una firma electr&oacute;nica. 
1052			 * Implementada en el applet Java de firma.
1053			 */
1054			function coSign (signB64, dataB64, algorithm, format, extraParams, successCallbackFunction, errorCallbackFunction) {
1055				successCallback = successCallbackFunction;
1056				errorCallback = errorCallbackFunction;
1057				signByService("cosign", signB64, algorithm, format, extraParams);
1058			}
1059
1060			/**
1061			 * Inicia el proceso de contrafirma de una firma electr&oacute;nica.
1062			 * Implementada en el applet Java de firma. 
1063			 */
1064			function counterSign (signB64, algorithm, format, extraParams, successCallbackFunction, errorCallbackFunction) {
1065				successCallback = successCallbackFunction;
1066				errorCallback = errorCallbackFunction;
1067				signByService("countersign", signB64, algorithm, format, extraParams);
1068			}
1069
1070			/**
1071			 * Realiza una operacion de firma/multifirma comunicandose con la aplicacion nativa por socket.
1072			 * @param signId Identificador de la operacion a realizar (sign, cosign y countersign).
1073			 * @param dataB64 Datos o firma en base 64.
1074			 * @param algorithm Algoritmo de firma.
1075			 * @param format Formato de firma.
1076			 * @param extraParams Par&aacute;metros para la configuraci&oacute;
1076n de la operaci&oacute;n.
1077			 */
1078			function signByService (signId, dataB64, algorithm, format, extraParams) {
1079
1080				if (dataB64 == undefined || dataB64 == "") {
1081					dataB64 = null;
1082				}
1083
1084				if (dataB64 != null && !isValidUrl(dataB64)) {
1085					dataB64 = dataB64.replace(/\+/g, "-").replace(/\//g, "_");
1086				}
1087
1088				var data = generateDataToSign(signId, algorithm, format, extraParams, dataB64, defaultKeyStore);
1089				
1090				execAppIntent(buildUrl(data));
1091			}
1092
1093			/**
1094			 * Inicia el proceso de firma por lotes.
1095			 */
1096			function signAndSaveToFile (opId, dataB64, algorithm, format, extraParams, outputFileName, successCallbackFunction, errorCallbackFunction) {
1097				successCallback = successCallbackFunction;
1098				errorCallback = errorCallbackFunction;
1099				signAndSaveFileByService(opId, dataB64, algorithm, format, extraParams, outputFileName);
1100			}
1101
1102			/**
1103			 * Realiza una operacion de firma/multifirma seguida del guardado del resultado comunicandose
1104			 * con la aplicacion nativa por socket.
1105			 * @param signId Identificador de la operacion a realizar (sign, cosign y countersign).
1106			 * @param dataB64 Datos o firma en base 64.
1107			 * @param algorithm Algoritmo de firma.
1108			 * @param format Formato de firma.
1109			 * @param extraParams Parametros para la configuraci&oacute;
1109n de la operaci&oacute;n.
1110			 */
1111			function signAndSaveFileByService (signId, dataB64, algorithm, format, extraParams, outputFileName) {
1112
1113				if (dataB64 == undefined || dataB64 == "") {
1114					dataB64 = null;
1115				}
1116
1117				if (dataB64 != null && !isValidUrl(dataB64)) {
1118					dataB64 = dataB64.replace(/\+/g, "-").replace(/\//g, "_");
1119				}
1120
1121				var data = generateDataToSignAndSave(signId, algorithm, format, extraParams, outputFileName, dataB64, defaultKeyStore);
1122
1123				execAppIntent(buildUrl(data));
1124			}
1125
1126			/**
1127			 * Inicia el proceso de firma por lotes.
1128			 */
1129			function signBatch (batchB64, batchPreSignerUrl, batchPostSignerUrl, extraParams, successCallbackFunction, errorCallbackFunction) {
1130				successCallback = successCallbackFunction;
1131				errorCallback = errorCallbackFunction;
1132				signBatchByService(batchB64, batchPreSignerUrl, batchPostSignerUrl, extraParams);
1133			}
1134			
1135			function signBatchByService (batchB64, batchPreSignerUrl, batchPostSignerUrl, extraParams) {
1136				
1137				if (batchB64 == undefined || batchB64 == "") {
1138					batchB64 = null;
1139				}
1140				
1141				if (batchB64 != null) {
1142					batchB64 = batchB64.replace(/\+/g, "-").replace(/\//g, "_");
1143				}
1144				
1145				var data = generateDataToBatch(defaultKeyStore, batchPreSignerUrl, batchPostSignerUrl, extraParams, batchB64);
1146				execAppIntent(buildUrl(data));
1147			}
1148			
1149			/**
1150			 * Genera el objeto con los datos de la transaccion para la firma
1151			 */
1152			function generateDataToBatch(keystore, batchPreSignerUrl, batchPostSignerUrl, extraParams, batchB64) {
1153				var data = new Object();
1154				data.op = generateDataKeyValue("op","batch");
1155				data.keystore = generateDataKeyValue("keystore", keystore != null ? keystore : null);
1156				data.ksb64 = generateDataKeyValue ("ksb64", keystore != null ? Base64.encode(keystore) : null);
1157				data.batchpresignerurl = generateDataKeyValue("batchpresignerurl", batchPreSignerUrl);
1158				data.batchpostsignerurl = generateDataKeyValue("batchpostsignerurl", batchPostSignerUrl);
1159				data.properties = generateDataKeyValue ("properties", extraParams != null ? Base64.encode(extraParams) : null);
1160				data.dat = generateDataKeyValue ("dat",  batchB64 == "" ? null : batchB64);
1161
1162				return data;
1163			}
1164
1165			/**
1166			 * Construye una URL para la invocaci&oacute;n del Cliente @firma nativo.
1167			 * params: Par\u00E1metros para la configuraci\u00F3n de la operaci\u00F3n.
1168			 */
1169			function buildUrl (arr) {
1170
1171				// Operacion seleccionada
1172				var intentURL;
1173				var params = [];
1174				// Convertimos el objeto con los datos en un array del tipo key value
1175				for(var x in arr){
1176				  params.push(arr[x]);
1177				}
1178
1179				if (params != null && params != undefined && params.length > 0) {
1180					intentURL = 'afirma://' + encodeURIComponent(arr.op.value) + '?';	// Agregamos como dominio el identificador del tipo de operacion
1181					for (var i = 0; i < params.length; i++) {
1182						if (params[i].value != null && params[i].value != "null") {
1183							intentURL += (i != 0 ? '&' : '') + params[i].key + '=' + encodeURIComponent(params[i].value); 
1184						}
1185					}
1186				}
1187				return intentURL;
1188			}
1189			
1190			function execAppIntent (url) {
1191				
1192				// Primera ejecucion, no hay puerto definido
1193				if (port == "") {
1194					// Calculamos los puertos
1195					var ports = getRandomPorts();
1196					// Invocamos a la aplicacion nativa
1197					openNativeApp(ports);
1198					// Enviamos la peticion a la app despues de esperar un tiempo prudencial
1199					setTimeout(executeEchoByServiceByPort, MiniApplet.AUTOFIRMA_LAUNCHING_TIME, ports, url);
1200				}
1201				// Se ha ejecutado anteriormente y tenemos un puerto calculado.
1202				else {
1203					connection = false;
1204					executeEchoByService (port, url, MiniApplet.AUTOFIRMA_CONNECTION_RETRIES)
1205				}
1206			}
1207			
1208			/**
1209			 * Obtiene un puerto aleatorio para la comunicaci\u00F3n con la aplicaci\u00F3n nativa.
1210			 */
1211			function getRandomPorts () {
1212				var MIN_PORT = 49152;
1213				var MAX_PORT = 65535;
1214				var ports = new Array();
1215				ports[0] = Math.floor((Math.random() * (MAX_PORT - MIN_PORT))) + MIN_PORT;
1216				ports[1] = Math.floor((Math.random() * (MAX_PORT - MIN_PORT))) + MIN_PORT;
1217				ports[2] = Math.floor((Math.random() * (MAX_PORT - MIN_PORT))) + MIN_PORT;
1218				return ports;
1219			}
1220		
1221		
1222			function openNativeApp (ports) {
1223			
1224				var portsLine = "";
1225				for (var i = 0; i < ports.length; i++) {
1226					portsLine += ports[i];
1227					if (i < (ports.length - 1)) {
1228						portsLine += ",";
1229					}
1230				}
1231				idSession = generateNewIdSession();
1232				openUrl("afirma://service?ports=" + portsLine + "&v=" + PROTOCOL_VERSION + "&idsession=" + idSession);
1233			}
1234			
1235			/**
1236			 * Llama a la aplicacion de firma a traves de la URL de invocacion sin que afecte
1237			 * a la pagina que se esta mostrando.
1238			 * @param url URL de invocacion.
1239			 */
1240			function openUrl (url) {
1241				
1242				// Usamos document.location porque tiene mejor soporte por los navegadores que
1243				// window.location que es el mecanismo estandar
1244				if (isChrome()) {
1245					document.location = url;
1246				}
1247				else {
1248					
1249					// Si ya existe el iframe, lo eliminamos para despues volverlo a crear 
1250					if (document.getElementById("iframeAfirma") != null) {
1251						try {
1252							var element = document.getElementById("iframeAfirma");
1253							element.outerHTML = "";
1254							delete element;
1255						}
1256						catch (e) {
1257							// No hacemos nada
1258						}
1259					}
1260					
1261					var iframeElem = document.createElement("iframe");
1262
1263					var idAttr = document.createAttribute("id");
1264					idAttr.value = "iframeAfirma";
1265					iframeElem.setAttributeNode(idAttr);
1266
1267					var srcAttr = document.createAttribute("src");
1268					srcAttr.value = url;
1269					iframeElem.setAttributeNode(srcAttr);
1270
1271					var heightAttr = document.createAttribute("height");
1272					heightAttr.value = 1;
1273					iframeElem.setAttributeNode(heightAttr);
1274
1275					var widthAttr = document.createAttribute("width");
1276					widthAttr.value = 1;
1277					iframeElem.setAttributeNode(widthAttr);
1278
1279					var styleAttr = document.createAttribute("style");
1280					styleAttr.value = "display: none;";
1281					iframeElem.setAttributeNode(styleAttr);
1282
1283					document.body.appendChild(iframeElem);
1284				}
1285			}
1286
1287			/**
1288			 * Genera el objeto con los datos de la transaccion para la operacion
1289			 * de firma/multifirma
1290			 */
1291			function generateDataToSign(signId, algorithm, format, extraParams, dataB64, keystore) {
1292				var data = new Object();
1293				data.op = generateDataKeyValue("op", signId);
1294				data.keystore = generateDataKeyValue ("keystore", keystore != null ? keystore : null);
1295				data.ksb64 = generateDataKeyValue ("ksb64", keystore != null ? Base64.encode(keystore) : null);
1296				data.algorithm = generateDataKeyValue ("algorithm", algorithm);
1297				data.format = generateDataKeyValue ("format", format); 
1298				data.properties = generateDataKeyValue ("properties", extraParams != null ? Base64.encode(extraParams) : null);
1299				data.dat = generateDataKeyValue ("dat", dataB64 == "" ? null : dataB64);
1300				return data;
1301			}
1302
1303			/**
1304			 * Genera el objeto con los datos de la transaccion para la operacion
1305			 * de firma/multifirma seguida del guardado del resultado
1306			 */
1307			function generateDataToSignAndSave(signId, algorithm, format, extraParams, outputFileName, dataB64, keystore) {
1308				var data = new Object();
1309				data.op = generateDataKeyValue("op", "signandsave");
1310				data.cryptoOp = generateDataKeyValue("cop", signId);
1311				data.keystore = generateDataKeyValue ("keystore", keystore != null ? keystore : null);
1312				data.ksb64 = generateDataKeyValue ("ksb64", keystore != null ? Base64.encode(keystore) : null);
1313				data.algorithm = generateDataKeyValue ("algorithm", algorithm);
1314				data.format = generateDataKeyValue ("format", format);
1315				data.properties = generateDataKeyValue ("properties", extraParams != null ? Base64.encode(extraParams) : null);
1316				data.filename = generateDataKeyValue ("filename", outputFileName);
1317				data.dat = generateDataKeyValue ("dat", dataB64 == "" ? null : dataB64);
1318				return data;
1319			}
1320			
1321			function executeEchoByServiceByPort (ports, url) {
1322				connection = false;
1323				var semaphore = new Object();
1324				semaphore.locked = false;
1325				executeEchoByService (ports[0], url, MiniApplet.AUTOFIRMA_CONNECTION_RETRIES, semaphore);
1326				executeEchoByService (ports[1], url, MiniApplet.AUTOFIRMA_CONNECTION_RETRIES, semaphore);
1327				executeEchoByService (ports[2], url, MiniApplet.AUTOFIRMA_CONNECTION_RETRIES, semaphore);
1328			}
1329
1330			/**
1331			* Intenta conectar con la aplicación nativa mandando una peticion echo al puerto.
1332			* Si la aplicación responde lanzamos la ejecucion del servicio.
1333			* Si la aplicación no responde volvemos a lanzar cada 2 segundos otra peticion echo hasta que una
1334			* peticion sea aceptada.
1335			*/
1336			function executeEchoByService (currentPort, url, timeoutResetCounter, semaphore) {
1337				var httpRequest = getHttpRequest();
1338				httpRequest.open("POST", URL_REQUEST + currentPort + "/afirma", true);
1339				httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
1340				httpRequest.onreadystatechange = function() {
1341					if (httpRequest.readyState == 4 && httpRequest.status == 200 && Base64.decode(httpRequest.responseText, true) == "OK" && !connection) {
1342						//console.log("puerto asignado puerto:" + currentPort);
1343						port = currentPort;
1344						urlHttpRequest = URL_REQUEST + port + "/afirma";
1345						connection = true;
1346						if (semaphore) {
1347							semaphore.locked = true;
1348						}
1349						// Comprobamos si es una operacion de seleccion de certificado
1350						isSelectCertOperation = url.indexOf("afirma://selectcert") > -1;
1351						// Comprobamos si es una operacion de guardado.
1352						isSaveOperation = url.indexOf("afirma://save") > -1;
1353						// Comprobamos si es una operacion de firma por lotes
1354						isBatchOperation = url.indexOf("afirma://batch") > -1;
1355						// Comprobamos si es una operacion criptografica mas guardado del resultado
1356						isOpAndSaveOperation = url.indexOf("afirma://signandsave") > -1;
1357						executeOperationByService(url);
1358					}
1359					else if ((!semaphore || !semaphore.locked) && !connection && httpRequest.readyState != 2 && httpRequest.readyState != 3) {
1360						timeoutResetCounter--;
1361						//console.log("Quedan " + timeoutResetCounter + " peticiones a " + currentPort)
1362						
1363						// Si ya se conecto antes con la aplicacion pero ahora llevamos la mitad de los intentos
1364						// sin conectar, consideramos que se ha tumbado y hay que relanzarla
1365						if (port != "" && timeoutResetCounter < MiniApplet.AUTOFIRMA_CONNECTION_RETRIES/2) {
1366							port = "";
1367							if (semaphore) {
1368								semaphore.locked = true;
1369							}
1370							timeoutResetCounter = MiniApplet.AUTOFIRMA_CONNECTION_RETRIES;
1371							execAppIntent(url);							
1372						}
1373						// Si hemos agotado todos los reintentos consideramos que la aplicacion no esta instalada
1374						else if (timeoutResetCounter == 0) {
1375							if (!semaphore || !semaphore.locked) {
1376								if (semaphore) {
1377									semaphore.locked = true;
1378								}
1379								errorCallback("es.gob.afirma.standalone.ApplicationNotFoundException", "No se ha podido conectar con AutoFirma.");
1380							}
1381							return;
1382						}
1383						// Aun quedan reintentos
1384						else {
1385							setTimeout(executeEchoByService, MiniApplet.AUTOFIRMA_LAUNCHING_TIME, currentPort, url, timeoutResetCounter, semaphore);
1386						}
1387					}
1388				}
1389				
1390				if (!connection) {
1391					// Mandamos un echo con - por lo que las variables de control se resetearan
1392					// Se anade EOF para que cuando el socket SSL lea la peticion del buffer sepa
1393					// que ha llegado al final y no se quede en espera
1394					httpRequest.send("echo=-idsession=" + idSession + "@EOF");
1395					//console.log("probamos puerto " +currentPort)
1396				}
1397			}
1398			
1399			/**
1400			* Comprueba si hay que dividir los datos que se se mandan a la a
1400plicacion nativa.
1401			* Si hay que dividirlos se llama a la funcion executeOperationRecursive.
1402			* Si cabe en un solo envio se manda directamente.
1403			*/
1404			function executeOperationByService (url) {
1405				
1406				// Si el envio se debe fragmentar, llamamos a una función que se encarga
1407				// de mandar la peticion recursivamente
1408				if (url.length > URL_MAX_SIZE) {
1409					executeOperationRecursive(url, 1, Math.ceil(url.length/URL_MAX_SIZE));
1410					return;
1411				}
1412				
1413				var httpRequest = getHttpRequest();
1414				httpRequest.open("POST", urlHttpRequest, true);
1415				httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");
1416				
1417				// El envio no se fragmenta
1418				httpRequest.onreadystatechange = function() {
1419					if (httpRequest.status == 404) {
1420						errorServiceResponseFunction("java.lang.IOException", httpRequest.responseText);
1421					}
1422					// Las operaciones que no requieren respuesta, llaman directamente a la funcion de exito 
1423					if (isSaveOperation) {
1424						if (httpRequest.readyState == 4 && Base64.decode(httpRequest.responseText) != "") {
1425							successServiceResponseFunction(Base64.decode(httpRequest.responseText));
1426						}
1427						return;
1428					}
1429					// El resto de operaciones deben componer el resultado
1430					else {
1431						if (httpRequest.readyState == 4 && httpRequest.status == 200 && httpRequest.responseText != "") {
1432							if (Base64.decode(httpRequest.responseText) == "MEMORY_ERROR"){
1433								errorServiceResponseFunction("java.lang.OutOfMemoryError", "Problema de memoria en servidor");
1434								return;
1435							}
1436							// Juntamos los fragmentos
1437							totalResponseRequest = "";
1438							addFragmentRequest (1, Base64.decode(httpRequest.responseText, true));
1439						}
1440						// Volvemos a mandar la peticion si no manda texto en la respuesta y la peticion esta en estado ready
1441						else if (httpRequest.responseText == "" && httpRequest.status == 0 && httpRequest.readyState == 0) {
1442							setTimeout(executeOperationByService, WAITING_TIME, url);
1443						}
1444					}
1445				}
1446				httpRequest.onerror = function(e) {
1447					// status error 0 es que no se ha podido comunicar con la aplicacion
1448					if (e.target.status == 0) {
1449						errorServiceResponseFunction("java.lang.IOException", "Se ha perdido la conexión con la aplicación @firma "+e.target.statusText);
1450					}
1451					// error desconocido 
1452					else {
1453						errorServiceResponseFunction("java.lang.IOException", "Ocurrio un error de red en la llamada al servicio de firma "+e.target.statusText);
1454					}
1455				}
1456				// se anade EOF para que cuando el socket SSL lea la peticion del buffer sepa que ha llegado al final y no se quede en espera
1457				httpRequest.send("cmd=" + Base64.encode(url, true) + "idsession=" + idSession + "@EOF");
1458			}
1459			
1460			/**
1461			* Manda los datos a la aplicación nativa en varios fragmentos porque ha habido que dividir los datos.
1462			* Se va mandando cada petición cuando se reciba la anterior.
1463			*/
1464			function executeOperationRecursive (url, i, iFinal) {
1465				
1466				var httpRequest = getHttpRequest();
1467				httpRequest.open("POST", urlHttpRequest, true);
1468				httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");
1469				var urlToSend = url.substring(((i-1) * URL_MAX_SIZE), Math.min(URL_MAX_SIZE * i, url.length));
1470				httpRequest.onreadystatechange = function (evt) {
1471					if (httpRequest.status == 404) {
1472						errorServiceResponseFunction("java.lang.Exception", httpRequest.responseText);
1473						return;
1474					}
1475					// Respuesta afirmativa, hay que mandar mas fragmentos
1476					if (httpRequest.readyState == 4 && httpRequest.status == 200 && httpRequest.responseText != "" ) {
1477						recibidos++;
1478						
1479						// Faltan mas peticiones por enviar
1480						if (Base64.decode(httpRequest.responseText, true) == "MORE_DATA_NEED") {
1481							if (recibidos < iFinal ){
1482								//console.log("recibido el fragmento "+recibidos + "de "+iFinal)
1483								executeOperationRecursive(url, i+1, iFinal);
1484							}
1485						}
1486						// Todas las peticiones se han recibido, hay que mandar operacion firma
1487						// Respuesta es OK
1488						else if (Base64.decode(httpRequest.responseText, true) == "OK") {
1489							if(recibidos == iFinal){
1490								//console.log("recibido todo, realizamos la operacion");
1491								recibidos = 0;
1492								doFirm();
1493							}
1494						}
1495						else if(Base64.decode(httpRequest.responseText) == "MEMORY_ERROR"){
1496							successServiceResponseFunction(Base64.decode(httpRequest.responseText));
1497						}
1498					}
1499					else if (httpRequest.responseText == "" && httpRequest.status == 0 && httpRequest.readyState == 0) {
1500						setTimeout(executeOperationRecursive, WAITING_TIME, url, i, iFinal);
1501					}
1502				}
1503				httpRequest.onerror = function(e) { 
1504					// Status error 0 es que no se ha podido comunicar con la aplicacion
1505					if (e.target.status == 0){
1506						errorServiceResponseFunction("java.lang.IOException", "Se ha perdido la conexión con la aplicación @firma "+e.target.statusText);
1507					}
1508					// Error desconocido 
1509					else{
1510						errorServiceResponseFunction("java.lang.IOException", "Ocurrio un error de red en la llamada al servicio de firma "+e.target.statusText);
1511					}
1512				}
1513				// Se anade EOF para que cuando el socket SSL lea la peticion del buffer sepa que ha llegado al final y no se quede en espera
1514				httpRequest.send("fragment=@" + i + "@" + iFinal + "@"  + Base64.encode(urlToSend, true) + "idsession=" + idSession +"@EOF");
1515				//console.log("mandado parte "+i+" de"+iFinal);
1516	
1517			}
1518			
1519			/**
1520			 * Realiza una operacion que se ha mandando en varios fragmentos.
1521			 */
1522			function doFirm () {
1523				httpRequest = getHttpRequest();
1524				httpRequest.open("POST", urlHttpRequest, true);
1525				httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");
1526				httpRequest.onreadystatechange = function() {
1527
1528					if (httpRequest.status == 404) {
1529						errorServiceResponseFunction("java.lang.Exception", httpRequest.responseText);
1530					}
1531
1532					// Se ha realizado la operacion save, no controlamos reintentos ni el exito de la peticion
1533					// porque no requiere respuesta
1534					if (isSaveOperation) {
1535						if (httpRequest.readyState == 4 && Base64.decode(httpRequest.responseText) != "") {
1536							successServiceResponseFunction(Base64.decode(httpRequest.responseText));
1537						}
1538						return;
1539					}
1540					else {
1541						if (httpRequest.readyState == 4 && httpRequest.status == 200 && httpRequest.responseText != "") {
1542							if(Base64.decode(httpRequest.responseText) == "MEMORY_ERROR"){
1543								successServiceResponseFunction(Base64.decode(httpRequest.responseText));
1544							}
1545							totalResponseRequest = "";
1546							addFragmentRequest (1, Base64.decode(httpRequest.responseText, true));
1547						}
1548						// No recibimos la respuesta, volvemos a llamar.
1549						else {
1550							if (httpRequest.status == 0 && httpRequest.readyState == 0 ) {
1551								setTimeout(doFirm, WAITING_TIME);	
1552							}
1553						}
1554					}
1555				}
1556				httpRequest.onerror = function(e) { 
1557					// status error 0 es que no se ha podido comunicar con la aplicacion
1558					if (e.target.status == 0){
1559						errorServiceResponseFunction("java.lang.IOException", "Se ha perdido la conexión con la aplicación @firma "+e.target.statusText);
1560					}
1561					// error desconocido 
1562					else{
1563						errorServiceResponseFunction("java.lang.IOException", "Ocurrio un error de red en la llamada al servicio de firma "+e.target.statusText);
1564					}
1565				}
1566				httpRequest.send("firm=idsession=" + idSession +"@EOF");
1567			}
1568
1569			/**
1570			 * Se encarga de solicitar y montar la respuesta de la operacion realizada.
1571			 */
1572			function addFragmentRequest (part, totalParts){
1573				httpRequest = getHttpRequest();
1574				httpRequest.open("POST", urlHttpRequest, true);
1575				httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");
1576				httpRequest.onreadystatechange = function() {
1577					if (httpRequest.readyState == 4 && httpRequest.status == 200 && httpRequest.responseText != "") {
1578						if(Base64.decode(httpRequest.responseText) == "MEMORY_ERROR"){
1579							errorServiceResponseFunction("java.lang.OutOfMemoryError", "Problema de memoria en servidor");
1580							return;
1581						}
1582						//console.log("recibida la parte " + part);
1583						totalResponseRequest += Base64.decode(httpRequest.responseText, true);
1584						// Si estan todas las partes llamamos al successcallback
1585						if (part == totalParts) {
1586							// Es una operacion de firma por lotes y tiene un callback propio
1587							if (isBatchOperation) {
1588								successBatchResponseFunction(totalResponseRequest);
1589								isBatchOperation = false;
1590							}
1591							// Es una operacion de seleccion de certificados y tiene un callback propio
1592							else if (isSelectCertOperation) {
1593								successSelectCertServiceResponseFunction(totalResponseRequest);
1594								isSelectCertOperation = false;
1595							}
1596							// Es cualquier otra operacion que requiere respuesta
1597							else {
1598								successServiceResponseFunction(totalResponseRequest);
1599							}
1600							totalResponseRequest = "";
1601						}
1602						else {
1603							addFragmentRequest(part + 1, totalParts);
1604						}
1605					}
1606					else {
1607						if (httpRequest.responseText == "" && httpRequest.status == 0 && httpRequest.readyState == 0){
1608							setTimeout(addFragmentRequest, WAITING_TIME, part, totalParts);
1609						}
1610					}
1611				}
1612				httpRequest.onerror = function(e) { 
1613					// status error 0 es que no se ha podido comunicar con la aplicacion
1614					if (e.target.status == 0){
1615						errorServiceResponseFunction("java.lang.IOException", "Se ha perdido la conexión con la aplicación @firma "+e.target.statusText);
1616					}
1617					// error desconocido 
1618					else{
1619						errorServiceResponseFunction("java.lang.IOException", "Ocurrio un error de red en la llamada al servicio de firma "+e.target.statusText);
1620					}
1621				}
1622				if (part <= totalParts){
1623					// se anade EOF para que cuando el socket SSL lea la peticion del buffer sepa que ha llegado al final y no se quede en espera
1624					httpRequest.send("send=@"+part+"@"+totalParts+"idsession=" + idSession +"@EOF");
1625					//console.log("solicitarmos la parte "+part+" de "+ totalParts)
1626				}
1627			}
1628			
1629			/**
1630			 * Lee el resultado devuelto por el servicio, 'CANCEL' o empieza por 'SAF-', ejecutara el metodo
1631			 * de error, si es 'OK' o cualquier otra cosa (que se intepretara como el resultado en base 64)
1632			 * se ejecutara el metodo de exito. En este ultimo caso, se descifrara el resultado. 
1633			 * @param data Resultado obtenido.
1634			 */
1635			function successSelectCertServiceResponseFunction (data) {
1636				// No se ha obtenido respuesta o se notifica la cancelacion
1637				if (data == undefined || data == null || data == "CANCEL") {
1638					errorCallback("es.gob.afirma.core.AOCancelledOperationException", "Operacion cancelada por el usuario");
1639					return;
1640				}
1641
1642				// Se ha producido un error
1643				if (data.length > 4 && data.substr(0, 4) == "SAF_") {
1644					errorCallback("java.lang.Exception", data);
1645					return;
1646				}
1647
1648				// Se ha producido un error y no se ha identificado el tipo
1649				if (data == "NULL") {
1650					errorCallback("java.lang.Exception", "Error desconocido");
1651					return;
1652				}
1653
1654				successCallback(data.replace(/\-/g, "+").replace(/\_/g, "/"));
1655			}
1656
1657			/**
1658			 * Lee el resultado devuelto por el servicio, 'CANCEL' o empieza por 'SAF-', ejecutara el metodo
1659			 * de error, si es 'OK' o cualquier otra cosa (que se intepretara como el resultado en base 64)
1660			 * se ejecutara el metodo de exito. En este ultimo caso, se descifrara el resultado. 
1661			 * @param data Resultado obtenido.
1662			 */
1663			function successServiceResponseFunction (data) {
1664				// No se ha obtenido respuesta o se notifica la cancelacion
1665				if (data == undefined || data == null || data == "CANCEL") {
1666					errorCallback("es.gob.afirma.core.AOCancelledOperationException", "Operacion cancelada por el usuario");
1667					return;
1668				}
1669				
1670				// Termina bien y no devuelve ningun resultado o es una operacion guardado
1671				if (data == "OK") {
1672					successCallback(data, null);
1673					return;
1674				}
1675				
1676				// Error de memoria
1677				if (data == "MEMORY_ERROR") {
1678					errorCallback("es.gob.afirma.core.OutOfMemoryError", "El fichero que se pretende firmar o guardar excede de la memoria disponible para aplicacion");
1679					return;
1680				}
1681				
1682				// Se ha producido un error
1683				if (data.length > 4 && data.substr(0, 4) == "SAF_") {
1684					errorCallback("java.lang.Exception", data);
1685					return;
1686				}
1687
1688				// Se ha producido un error y no se ha identificado el tipo
1689				if (data == "NULL") {
1690					errorCallback("java.lang.Exception", "Error desconocido");
1691					return;
1692				}
1693				
1694				// Operacion guardado con exito
1695				if (data == "SAVE_OK") {
1696					if (successCallback) {
1697						successCallback(data);
1698					}
1699					return;
1700				}
1701
1702				// Interpretamos el resultado como un base 64 y el certificado y los datos cifrados
1703				var signature;
1704				var certificate = null;
1705				var sepPos = data.indexOf("|");
1706				
1707				if (sepPos == -1) {
1708					signature = Base64.decode(data, true).replace(/\-/g, "+").replace(/\_/g, "/");
1709				}
1710				else {
1711					certificate = data.substring(0, sepPos).replace(/\-/g, "+").replace(/\_/g, "/");
1712					signature = data.substring(sepPos + 1).replace(/\-/g, "+").replace(/\_/g, "/");
1713				}
1714				successCallback(signature, certificate);
1715			}
1716			
1717			function errorServiceResponseFunction(exception, message){
1718				errorCallback(exception, message);
1719			}
1720			
1721			function successBatchResponseFunction (data) {
1722				// No se ha obtenido respuesta o se notifica la cancelacion
1723				if (data == undefined || data == null || data == "CANCEL") {
1724					errorCallback("es.gob.afirma.core.AOCancelledOperationException", "Operacion cancelada por el usuario");
1725					return;
1726				}
1727				
1728				// Se ha producido un error
1729				if (data.length > 4 && data.substr(0, 4) == "SAF_") {
1730					errorCallback("java.lang.Exception", data);
1731					return;
1732				}
1733
1734				// Se ha producido un error y no se ha identificado el tipo
1735				if (data == "NULL") {
1736					errorCallback("java.lang.Exception", "Error desconocido");
1737					return;
1738				}
1739				successCallback(data);
1740			}
1741			
1742			/**
1743			 * Inicia el proceso de guardado de una firma.
1744			 */
1745			function saveDataToFile (dataB64, title, filename, extension, description, successCallbackSave, errorCallbackSave) {
1746				successCallback = successCallbackSave;
1747				errorCallback = errorCallbackSave;
1748				saveByService(dataB64, title, filename, extension, description);
1749			}
1750
1751			function saveByService ( dataB64, title, filename, extension, description) {
1752				
1753				if (dataB64 == undefined || dataB64 == "") {
1754					dataB64 = null;
1755				}
1756
1757				if (dataB64 != null && !isValidUrl(dataB64)) {
1758					dataB64 = dataB64.replace(/\+/g, "-").replace(/\//g, "_");
1759				}
1760
1761				var data = generateDataToSave(dataB64, title, filename, extension, description);
1762				
1763				execAppIntent(buildUrl(data));
1764			}
1765
1766			/**
1767			* Genera el objeto de datos para la operacion de guardar
1768			*/
1769			function generateDataToSave(dataB64, title, filename, extension, description) {
1770				var data = new Object();
1771				data.op = generateDataKeyValue ("op", "save");
1772				data.title = generateDataKeyValue ("title", title);
1773				data.filename = generateDataKeyValue ("filename", filename);
1774				data.extension = generateDataKeyValue ("extension", extension);
1775				data.description = generateDataKeyValue ("description", description);
1776				data.dat = generateDataKeyValue ("dat",  dataB64 == "" ? null : dataB64);
1777				
1778				return data;
1779			}
1780
1781			function generateDataKeyValue(key, value) {
1782				var data  = new Object();
1783				data.key = key;
1784				data.value = value;
1785				return data;
1786			}
1787			
1788			/**
1789			 * Convierte texto plano en texto base 64.
1790			 * Implementada en el applet Java de firma.
1791			 */
1792			function getBase64FromText (plainText, charset) {
1793				return plainText != null ? Base64.encode(plainText) : null;
1794			}
1795
1796			/**
1797			 * Convierte texto base 64 en texto plano.
1798			 * Implementada en el applet Java de firma.
1799			 */
1800			function getTextFromBase64 (base64Text, charset) {
1801				return base64Text != null ? Base64.decode(base64Text) : null;
1802			}
1803
1804			
1805			/**
1806			 * Carga de un fichero. Operacion no soportada. 
1807			 * Implementada en el applet Java de firma.
1808			 */
1809			function getFileNameContentBase64 (title, extensions, description) {
1810				throwException(UnsupportedOperationException, "La operacion de carga de ficheros no esta soportada");
1811			}
1812
1813			/**
1814			 * Carga de multiples ficheros. Operacion no soportada.
1815			 * Implementada en el applet Java de firma.
1816			 */
1817			function getMultiFileNameContentBase64 (title, extensions, description) {
1818				throwException(UnsupportedOperationException, "La operacion de carga de multiples ficheros no esta soportada");
1819			}
1820
1821			/** 
1822			 * Funcion para la comprobacion de existencia del objeto. No hace nada.
1823			 * Implementada en el applet Java de firma.
1824			 */
1825			function echo () {
1826				return "Cliente JavaScript";
1827			}
1828
1829			/** 
1830			 * No hace nada.
1831			 * Implementada en el applet Java de firma.
1832			 */
1833			function setStickySignatory (sticky) {
1834				// No hace nada
1835			}
1836
1837			/**
1838			 * Recupera el mensaje de error asociado al ultimo error capturado.
1839			 * Implementada en el applet Java de firma.
1840			 */
1841			function getErrorMessage () {
1842				return errorMessage;
1843			}
1844
1845			/**
1846			 * Recupera el tipo del ultimo error capturado.
1847			 * Implementada en el applet Java de firma.
1848			 */
1849			function getErrorType () {
1850				return errorType;
1851			}
1852
1853			/**
1854			 * Recupera el log de la aplicacion. Actualmente, el log solo esta
1855			 * disponible en el applet, no en las aplicacion moviles.
1856			 */
1857			function getCurrentLog () {
1858				return "Applet no cargado";
1859			}
1860
1861			/**
1862			 * Funcion para identificar el tipo de objeto del Cliente (javascript, applet,...).
1863			 */
1864			function getType () {
1865				return "javascript";
1866			}
1867
1868			/**
1869			 * Establece el error indicado como error interno y lanza una excepcion.
1870			 */
1871			function throwException (type, message) {
1872				errorType = type;
1873				errorMessage = message;
1874				throw new Error();
1875			}
1876
1877			/* Metodos que publicamos del objeto AppAfirmaJS */
1878			return {
1879				echo : echo,
1880				checkTime : checkTime,
1881				setKeyStore : setKeyStore,
1882				sign : sign,
1883				coSign : coSign,
1884				counterSign : counterSign,
1885				signBatch : signBatch,
1886				selectCertificate : selectCertificate,
1887				saveDataToFile : saveDataToFile,
1888				signAndSaveToFile : signAndSaveToFile,
1889				getFileNameContentBase64: getFileNameContentBase64,
1890				getMultiFileNameContentBase64 : getMultiFileNameContentBase64,
1891				getBase64FromText : getBase64FromText,
1892				getTextFromBase64 : getTextFromBase64,
1893				setStickySignatory : setStickySignatory,
1894				setLocale : setLocale,
1895				getErrorMessage : getErrorMessage,
1896				getErrorType : getErrorType,
1897				getCurrentLog : getCurrentLog				
1898			}
1899	});
1900		
1901		
1902		/**
1903		 * Objeto JavaScript que va a reemplazar al cliente de firma en los entornos en los que
1904		 * no pueden ejecutarse applets.
1905		 */
1906		var AppAfirmaJSWebService = ( function (clientAddress, window, undefined) {
1907
1908			var UnsupportedOperationException = "java.lang.UnsupportedOperationException";
1909
1910			/* Longitud maxima de una URL en Android para la invocacion de una aplicacion nativa. */
1911			var MAX_LONG_ANDROID_URL = 2000;
1912			
1913			/* Longitud maxima de una URL en iOS para la invocacion de una aplicacion nativa. */
1914			var MAX_LONG_IOS_URL = 80000;
1915			
1916			/* Longitud maxima de una URL en Windows 8 para la invocacion de una aplicacion nativa. */
1917			var MAX_LONG_WINDOWS8_URL = 2000;
1918			
1919			/* Longitud maxima que generalmente se permite a una URL. */
1920			var MAX_LONG_GENERAL_URL = 2000;
1921			
1922			/** Version del protocolo utilizada. */
1923			var PROTOCOL_VERSION = 1;
1924			
1925			/* Indica si ya se ha detectado que la aplicacion no esta instalada  */
1926			var wrongInstallation = true;
1927			
1928			/**
1929			 *  Atributos para la configuracion del objeto sustituto del applet Java de firma
1930			 */
1931			var errorMessage = '';
1932			var errorType = '';
1933			var defaultKeyStore = null;
1934			var retrieverServletAddress = null;
1935			var storageServletAddress = null;
1936
1937			if (clientAddress != undefined && clientAddress != null) {
1938				if (clientAddress.indexOf("://") != -1 && clientAddress.indexOf("/", clientAddress.indexOf("://") + 3) != -1) {
1939					var servletsBase = clientAddress.substring(0, clientAddress.indexOf("/", clientAddress.indexOf("://") + 3));
1940					retrieverServletAddress = servletsBase + "/afirma-signature-retriever/RetrieveService";
1941					storageServletAddress = servletsBase + "/afirma-signature-storage/StorageService";
1942				} else {
1943					retrieverServletAddress = clientAddress + "/afirma-signature-retriever/RetrieveService";
1944					storageServletAddress = clientAddress + "/afirma-signature-storage/StorageService";
1945				}
1946			}
1947
1948			/**
1949			 * Establece el almacen de certificados de que se debe utilizar por defecto.
1950			 */
1951			function setKeyStore (keystore) {
1952				defaultKeyStore = keystore;
1953			}
1954
1955			/**
1956			 * Inicia el proceso de seleccion de certificado.
1957			 * Implementada en el applet Java de firma
1958			 */
1959			function selectCertificate (extraParams, successCallback, errorCallback) {
1960				throwException(UnsupportedOperationException, "La operacion de seleccion de certificados no esta soportada");
1961			}
1962			
1963			/**
1964			 * Inicia el proceso de firma electronica.
1965			 * Implementada en el applet Java de firma
1966			 */
1967			function sign (dataB64, algorithm, format, extraParams, successCallback, errorCallback) {
1968				signOperation("sign", dataB64, algorithm, format, extraParams, successCallback, errorCallback);
1969			}
1970
1971			/**
1972			 * Inicia el proceso de cofirma de una firma electr&oacute;nica. 
1973			 * Implementada en el applet Java de firma.
1974			 */
1975			function coSign (signB64, dataB64, algorithm, format, extraParams, successCallback, errorCallback) {
1976				signOperation("cosign", signB64, algorithm, format, extraParams, successCallback, errorCallback);
1977			}
1978
1979			/**
1980			 * Inicia el proceso de contrafirma de una firma electr&oacute;nica.
1981			 * Implementada en el applet Java de firma. 
1982			 */
1983			function counterSign (signB64, algorithm, format, extraParams, successCallback, errorCallback) {
1984				signOperation("countersign", signB64, algorithm, format, extraParams, successCallback, errorCallback);
1985			}
1986			
1987			/**
1988			 * Realiza una operacion de firma/multifirma.
1989			 * @param signId Identificador de la operacion a realizar (sign, cosign y countersign).
1990			 * @param dataB64 Datos o firma en base 64.
1991			 * @param algorithm Algoritmo de firma.
1992			 * @param format Formato de firma.
1993			 * @param extraParams Par&aacute;metros para la configuraci&oacute;n de la operaci&oacute;n.
1994			 * @param successCallback M&eacute;todo a ejecutar en caso de &eacute;xito.
1995			 * @param errorCallback M&eacute;todo a ejecutar en caso de error.
1996			 */
1997			function signOperation (signId, dataB64, algorithm, format, extraParams, successCallback, errorCallback) {
1998
1999				if (dataB64 == undefined || dataB64 == "") {
2000					dataB64 = null;
2001				}
2002
2003				if (dataB64 != null && !isValidUrl(dataB64)) {
2004					dataB64 = dataB64.replace(/\+/g, "-").replace(/\//g, "_");
2005					dataB64 = dataB64.replace(/\n/g, "").replace(/\r/g, ""); //eliminamos saltos de carro para que no generen espacios 0x20 al parsear los atributos del XML enviado/recibido (storageServletAddress y retrieverServletAddress) que impiden la firma en AutoFirma
2006				}
2007
2008				var idSession = generateNewIdSession();
2009				var cipherKey = generateCipherKey();
2010
2011				var i = 0;
2012				var params = new Array();
2013				params[i++] = {key:"ver", value:PROTOCOL_VERSION};
2014				if (signId != null && signId != undefined) {			params[i++] = {key:"op", value:signId}; }
2015				if (idSession != null && idSession != undefined) {		params[i++] = {key:"id", value:idSession}; }
2016				if (cipherKey != null && cipherKey != undefined) {		params[i++] = {key:"key", value:cipherKey}; }
2017				if (defaultKeyStore != null &&
2018						defaultKeyStore != undefined) {					params[i++] = {key:"keystore", value:defaultKeyStore};
2019																		params[i++] = {key:"ksb64", value:Base64.encode(defaultKeyStore)}; }
2020				if (storageServletAddress != null &&
2021						storageServletAddress != undefined) {			params[i++] = {key:"stservlet", value:storageServletAddress}; }
2022				if (format != null && format != undefined) {			params[i++] = {key:"format", value:format}; }
2023				if (algorithm != null && algorithm != undefined) {		params[i++] = {key:"algorithm", value:algorithm}; }
2024				if (extraParams != null && extraParams != undefined) { 	params[i++] = {key:"properties", value:Base64.encode(extraParams)}; }
2025				if (dataB64 != null) {									params[i++] = {key:"dat", value:dataB64}; }
2026			
2027				var url = buildUrl(signId, params);
2028
2029				// Si la URL es muy larga, realizamos un preproceso para que los datos se suban al
2030				// servidor y la aplicacion nativa los descargue, en lugar de pasarlos directamente 
2031				if (isURLTooLong(url)) {
2032					if (storageServletAddress == null || storageServletAddress == undefined) {
2033						throwException("java.lang.IllegalArgumentException", "No se ha indicado la direccion del servlet para el guardado de datos");
2034						return;
2035					}
2036
2037					sendDataAndExecAppIntent(idSession, cipherKey, storageServletAddress, retrieverServletAddress, signId, params, successCallback, errorCallback)
2038				}
2039				else {
2040					execAppIntent(url, idSession, cipherKey, successCallback, errorCallback);
2041				}
2042			}
2043
2044			/**
2045			 * Realiza una operacion de firma/multifirma y permite guardar el fichero a disco.
2046			 * @param signId Identificador de la operacion a realizar (sign, cosign y countersign).
2047			 * @param dataB64 Datos o firma en base 64.
2048			 * @param algorithm Algoritmo de firma.
2049			 * @param format Formato de firma.
2050			 * @param extraParams Par&aacute;metros para la configuraci&oacute;
2050n de la operaci&oacute;n.
2051			 * @param outputFileName Nombre propuesto para el fichero a guardar.
2052			 * @param successCallback M&eacute;todo a ejecutar en caso de &eacute;xito.
2053			 * @param errorCallback M&eacute;todo a ejecutar en caso de error.
2054			 */
2055			function signAndSaveToFile (signId, dataB64, algorithm, format, extraParams, outputFileName, successCallback, errorCallback) {
2056
2057				if (dataB64 == undefined || dataB64 == "") {
2058					dataB64 = null;
2059				}
2060
2061				if (dataB64 != null && !isValidUrl(dataB64)) {
2062					dataB64 = dataB64.replace(/\+/g, "-").replace(/\//g, "_");
2063					dataB64 = dataB64.replace(/\n/g, "").replace(/\r/g, ""); //eliminamos saltos de carro para que no generen espacios 0x20 al parsear los atributos del XML enviado/recibido (storageServletAddress y retrieverServletAddress) que impiden la firma en AutoFirma
2064				}
2065
2066				var idSession = generateNewIdSession();
2067				var cipherKey = generateCipherKey();
2068
2069				var i = 0;
2070				var opId = "signandsave";
2071				var params = new Array();
2072				
2073				params[i++] = {key:"ver", value:PROTOCOL_VERSION};
2074				params[i++] = {key:"op", value:opId};
2075				if (signId != null && signId != undefined) {			params[i++] = {key:"cop", value:signId}; }
2076				if (idSession != null && idSession != undefined) {		params[i++] = {key:"id", value:idSession}; }
2077				if (cipherKey != null && cipherKey != undefined) {		params[i++] = {key:"key", value:cipherKey}; }
2078				if (defaultKeyStore != null &&
2079						defaultKeyStore != undefined) {					params[i++] = {key:"keystore", value:defaultKeyStore};
2080																		params[i++] = {key:"ksb64", value:Base64.encode(defaultKeyStore)}; }
2081				if (storageServletAddress != null &&
2082						storageServletAddress != undefined) {			params[i++] = {key:"stservlet", value:storageServletAddress}; }
2083				if (format != null && format != undefined) {			params[i++] = {key:"format", value:format}; }
2084				if (algorithm != null && algorithm != undefined) {		params[i++] = {key:"algorithm", value:algorithm}; }
2085				if (extraParams != null && extraParams != undefined) { 	params[i++] = {key:"properties", value:Base64.encode(extraParams)}; }
2086				if (outputFileName != null &&
2087						outputFileName != undefined) {					params[i++] = {key:"filename", value:outputFileName}; }
2088				if (dataB64 != null) {									params[i++] = {key:"dat", value:dataB64}; }
2089			
2090				var url = buildUrl(opId, params);
2091
2092				// Si la URL es muy larga, realizamos un preproceso para que los datos se suban al
2093				// servidor y la aplicacion nativa los descargue, en lugar de pasarlos directamente 
2094				if (isURLTooLong(url)) {
2095					if (storageServletAddress == null || storageServletAddress == undefined) {
2096						throwException("java.lang.IllegalArgumentException", "No se ha indicado la direccion del servlet para el guardado de datos");
2097						return;
2098					}
2099
2100					sendDataAndExecAppIntent(idSession, cipherKey, storageServletAddress, retrieverServletAddress, opId, params, successCallback, errorCallback)
2101				}
2102				else {
2103					execAppIntent(url, idSession, cipherKey, successCallback, errorCallback);
2104				}
2105			}
2106			
2107			/**
2108			 * Ejecuta una operacion de firma de lote.
2109			 */
2110			function signBatch (batchB64, batchPreSignerUrl, batchPostSignerUrl, extraParams, successCallback, errorCallback) {
2111				
2112				if (batchB64 == undefined || batchB64 == "") {
2113					batchB64 = null;
2114				}
2115
2116				if (batchB64 != null && !isValidUrl(batchB64)) {
2117					batchB64 = batchB64.replace(/\+/g, "-").replace(/\//g, "_");
2118					batchB64 = batchB64.replace(/\n/g, "").replace(/\r/g, ""); //eliminamos saltos de carro para que no generen espacios 0x20 al parsear los atributos del XML enviado/recibido (storageServletAddress y retrieverServletAddress) que impiden la firma en AutoFirma
2119				}
2120
2121				var idSession = generateNewIdSession();
2122				var cipherKey = generateCipherKey();
2123				
2124				var opId = "batch";
2125
2126				var i = 0;
2127				var params = new Array();
2128				params[i++] = {key:"ver", value:PROTOCOL_VERSION};
2129				params[i++] = {key:"op", value:opId};
2130				if (idSession != null && idSession != undefined) {		params[i++] = {key:"id", value:idSession}; }
2131				if (cipherKey != null && cipherKey != undefined) {		params[i++] = {key:"key", value:cipherKey}; }
2132				if (defaultKeyStore != null &&
2133						defaultKeyStore != undefined) {					params[i++] = {key:"keystore", value:defaultKeyStore};
2134																		params[i++] = {key:"ksb64", value:Base64.encode(defaultKeyStore)}; }
2135				if (storageServletAddress != null &&
2136						storageServletAddress != undefined) {			params[i++] = {key:"stservlet", value:storageServletAddress}; }
2137				if (batchPreSignerUrl != null &&
2138						batchPreSignerUrl != undefined) {				params[i++] = {key:"batchpresignerurl", value:batchPreSignerUrl}; }				
2139				if (batchPostSignerUrl != null &&
2140						batchPostSignerUrl != undefined) {				params[i++] = {key:"batchpostsignerurl", value:batchPostSignerUrl}; }
2141				if (extraParams != null && extraParams != undefined) { 	params[i++] = {key:"properties", value:Base64.encode(extraParams)}; }
2142				if (batchB64 != null) {									params[i++] = {key:"dat", value:batchB64}; }
2143
2144				var url = buildUrl(opId, params);
2145
2146				// Si la URL es muy larga, realizamos un preproceso para que los datos se suban al
2147				// servidor y la aplicacion nativa los descargue, en lugar de pasarlos directamente 
2148				if (isURLTooLong(url)) {
2149					if (storageServletAddress == null || storageServletAddress == undefined) {
2150						throwException("java.lang.IllegalArgumentException", "No se ha indicado la direccion del servlet para el guardado de datos");
2151						return;
2152					}
2153
2154					sendDataAndExecAppIntent(idSession, cipherKey, storageServletAddress, retrieverServletAddress, opId, params, successCallback, errorCallback)
2155				}
2156				else {
2157					execAppIntent(url, idSession, cipherKey, successCallback, errorCallback);
2158				}
2159			}
2160			
2161			/**
2162			 * Convierte texto plano en texto base 64.
2163			 * Implementada en el applet Java de firma.
2164			 */
2165			function getBase64FromText (plainText, charset) {
2166				return plainText != null ? Base64.encode(plainText) : null ;
2167			}
2168
2169			/**
2170			 * Convierte texto base 64 en texto plano.
2171			 * Implementada en el applet Java de firma.
2172			 */
2173			function getTextFromBase64 (base64Text, charset) {
2174				return Base64.decode(base64Text);
2175			}
2176			
2177			/**
2178			 * Guardado de datos en disco. Se realiza mediante la invocacion de una app nativa. 
2179			 */
2180			function saveDataToFile (dataB64, title, filename, extension, description, successCallback, errorCallback) {
2181
2182				if (dataB64 != undefined && dataB64 != null && dataB64 != "") {
2183					dataB64 = dataB64.replace(/\+/g, "-").replace(/\//g, "_");
2184				}
2185
2186				var idSession = generateNewIdSession();
2187				var cipherKey = generateCipherKey();
2188
2189				var i = 0;
2190				var opId = "save";
2191				var params = new Array();
2192				params[i++] = {key:"ver", value:PROTOCOL_VERSION};
2193				params[i++] = {key:"op", value:opId};
2194				if (idSession != null && idSession != undefined) {		params[i++] = {key:"id", value:idSession}; }
2195				if (cipherKey != null && cipherKey != undefined) {		params[i++] = {key:"key", value:cipherKey}; }
2196				if (storageServletAddress != null &&
2197						storageServletAddress != undefined) {			params[i++] = {key:"stservlet", value:storageServletAddress}; }
2198				if (title != null && title != undefined) {				params[i++] = {key:"title", value:title}; }
2199				if (filename != null && filename != undefined) {		params[i++] = {key:"filename", value:filename}; }
2200				if (extension != null && extension != undefined) {		params[i++] = {key:"extension", value:extension}; }
2201				if (description != null && description != undefined) {	params[i++] = {key:"description", value:description}; }
2202				if (dataB64 != null && dataB64 != undefined && dataB64 != "") {			params[i++] = {key:"dat", value:dataB64}; }
2203
2204				var url = buildUrl(opId, params);
2205
2206				// Si la URL es muy larga, realizamos un preproceso para que los datos se suban al
2207				// servidor y la aplicacion nativa los descargue, en lugar de pasarlos directamente 
2208				if (isURLTooLong(url)) {
2209					if (storageServletAddress == null || storageServletAddress == undefined) {
2210						throwException("java.lang.IllegalArgumentException", "No se ha indicado la direccion del servlet para el guardado de datos");
2211						return;
2212					}
2213
2214					sendDataAndExecAppIntent(idSession, cipherKey, storageServletAddress, retrieverServletAddress, opId, params, successCallback, errorCallback)
2215				}
2216				else {
2217					execAppIntent(url, idSession, cipherKey, successCallback, errorCallback);
2218				}
2219			}
2220
2221			/**
2222			 * Carga de un fichero. Operacion no soportada. 
2223			 * Implementada en el applet Java de firma.
2224			 */
2225			function getFileNameContentBase64 (title, extensions, description) {
2226				throwException(UnsupportedOperationException, "La operacion de carga de ficheros no esta soportada");
2227			}
2228
2229			/**
2230			 * Carga de multiples ficheros. Operacion no soportada.
2231			 * Implementada en el applet Java de firma.
2232			 */
2233			function getMultiFileNameContentBase64 (title, extensions, description) {
2234				throwException(UnsupportedOperationException, "La operacion de carga de multiples ficheros no esta soportada");
2235			}
2236
2237			/** 
2238			 * Funcion para la comprobacion de existencia del objeto. No hace nada.
2239			 * Implementada en el applet Java de firma.
2240			 */
2241			function echo () {
2242				return "Cliente JavaScript";
2243			}
2244
2245			/** 
2246			 * No hace nada.
2247			 * Implementada en el applet Java de firma.
2248			 */
2249			function setStickySignatory (sticky) {
2250				// No hace nada
2251			}
2252
2253			/**
2254			 * Recupera el mensaje de error asociado al ultimo error capturado.
2255			 * Implementada en el applet Java de firma.
2256			 */
2257			function getErrorMessage () {
2258				return errorMessage;
2259			}
2260
2261			/**
2262			 * Recupera el tipo del ultimo error capturado.
2263			 * Implementada en el applet Java de firma.
2264			 */
2265			function getErrorType () {
2266				return errorType;
2267			}
2268
2269			/**
2270			 * Recupera el log de la aplicacion. Actualmente, el log solo esta
2271			 * disponible en el applet, no en las aplicacion moviles.
2272			 */
2273			function getCurrentLog () {
2274				return "Applet no cargado";
2275			}
2276
2277			/**
2278			 * Funcion para identificar el tipo de objeto del Cliente (javascript, applet,...).
2279			 */
2280			function getType () {
2281				return "javascript";
2282			}
2283
2284			/**
2285			 * Establece las rutas de los servlets encargados de almacenar y recuperar las firmas de los dispositivos moviles.
2286			 */
2287			function setServlets (storageServlet,  retrieverServlet) {
2288				storageServletAddress = storageServlet;
2289				retrieverServletAddress = retrieverServlet;
2290			}
2291
2292			/**
2293			 * Establece el error indicado como error interno y lanza una excepcion.
2294			 */
2295			function throwException (type, message) {
2296				errorType = type;
2297				errorMessage = message;
2298				throw new Error();
2299			}
2300
2301
2302			/**
2303			 * Comprueba si una URL es demasiado larga para ser usada en una llamada
2304			 * GET en un Sistema/Navegador concreto.
2305			 */
2306			function isURLTooLong(url) {
2307				if (isAndroid()) {
2308					return url.length > MAX_LONG_ANDROID_URL;
2309				}
2310				else if (isIOS()) {
2311					return url.length > MAX_LONG_IOS_URL;
2312				}
2313				else if (isWindows8()) {
2314					return url.length > MAX_LONG_WINDOWS8_URL;
2315				}
2316				return url.length > MAX_LONG_GENERAL_URL;
2317			}
2318			
2319			/* Mayor entero. */
2320			var MAX_NUMBER = 2147483648;
2321
2322			/* Caracteres validos para los ID de sesion */
2323			var VALID_CHARS_TO_ID = "1234567890abcdefghijklmnopqrstuwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
2324
2325			/* Genera un identificador de sesion. */
2326			function generateNewIdSession () {
2327				var ID_LENGTH = 20;
2328				var random = "";
2329				var randomInts;
2330				if (typeof window.crypto != "undefined" && typeof window.crypto.getRandomValues != "undefined") {
2331					randomInts = new Uint32Array(ID_LENGTH);
2332					window.crypto.getRandomValues(randomInts);
2333				}
2334				else {
2335					randomInts = new Array(ID_LENGTH);
2336					for (var i = 0; i < ID_LENGTH; i++) {
2337						randomInts[i] = rnd() * MAX_NUMBER;
2338					}
2339				}
2340
2341				for (var i = 0; i < ID_LENGTH; i++) {
2342					random += VALID_CHARS_TO_ID.charAt(Math.floor(randomInts[i] % VALID_CHARS_TO_ID.length));
2343				}
2344
2345				return random;
2346			}
2347
2348			/* Genera un numero aleatorio para utilizar como clave de cifrado. */
2349			function generateCipherKey() {
2350				var random;
2351				if (typeof window.crypto != "undefined" && typeof window.crypto.getRandomValues != "undefined") {
2352					var randomInts = new Uint32Array(1);
2353					window.crypto.getRandomValues(randomInts);
2354					random = zeroFill(randomInts[0] % 100000000, 8);
2355				}
2356				else {
2357					random = zeroFill(Math.floor(((rnd() * MAX_NUMBER) + 1) % 100000000), 8);
2358				}
2359
2360				return random;
2361			}
2362
2363			/* Completa un numero con ceros a la izquierda. */
2364			function zeroFill(number, width) {
2365				width -= number.toString().length;
2366				if (width > 0) {
2367					return new Array(width + (/\./.test(number) ? 2 : 1)).join('0')
2368					+ number;
2369				}
2370				return number + "";
2371			}
2372
2373			/**
2374			 * Genera numeros aleatorios con una distribucion homogenea
2375			 */
2376			var seed;
2377			function rnd () {
2378				if (seed == undefined) {
2379					seed = new Date().getMilliseconds() * 1000 * Math.random();
2380				}
2381			    seed = (seed * 9301 + 49297) % 233280;
2382			    return seed / 233280;
2383			}
2384
2385			/**
2386			 * Envia los datos al servidor intermedio y luego invoca a la
2387			 * aplicacion nativa para que los descargue y opere con ellos.
2388			 * @param idSession Identificador de la operacion con el que se espera recuperar el resultado.
2389			 * @param cipherKey Clave de cifrado. Si no se indica, no se cifra.
2390			 * @param storageServletAddress URL del servlet que almacena.
2391			 * @param retrieverServletAddress URL del servlet que recupera.
2392			 * @param op Identificador del tipo de operacion (firma, cofirma, guardado,...).
2393			 * @param params Parametros de configuracion de la operacion.
2394			 * @param successCallback Funcion callback que debe ejecutarse en caso de exito.
2395			 * @param errorCallback Funcion callback que debe ejecutarse en caso de error.
2396			 */
2397			function sendDataAndExecAppIntent(idSession, cipherKey, storageServletAddress, retrieverServletAddress, op, params, successCallback, errorCallback) {
2398
2399				// Identificador del fichero (equivalente a un id de sesion) del que deben recuperarse los datos
2400				var fileId = generateNewIdSession(); 
2401
2402				// Subimos los datos al servidor intermedio
2403				var httpRequest = getHttpRequest();
2404				if (!httpRequest) {
2405					throwException("java.lang.Exception", "Su navegador no permite preprocesar los datos que desea tratar");
2406				}
2407				httpRequest.open("POST", storageServletAddress, true);
2408				httpRequest.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
2409
2410				httpRequest.onreadystatechange = function () {
2411					if (httpRequest.readyState == 4) {
2412						 if (httpRequest.status == 200) {
2413	
2414							url = buildUrlWithoutData(op, fileId, retrieverServletAddress, cipherKey);
2415							if (isURLTooLong(url)) {
2416								errorCallback("java.lang.IllegalArgumentException", "La URL de invocacion al servicio de firma es demasiado larga.");
2417								return;
2418							}
2419							execAppIntent(url, idSession, cipherKey, successCallback, errorCallback);
2420						}
2421						else {
2422							errorCallback("java.lang.IOException", "Ocurrio un error al enviar los datos a la aplicacion nativa");
2423						}
2424					}
2425				}
2426				
2427				httpRequest.onerror = function(e) {
2428					errorCallback("java.lang.IOException", "Ocurrio un error al enviar los datos al servicio intermedio para la comunicacion con la aplicacion nativa");
2429				}
2430
2431				var requestData =
2432					"op=put&v=1_0&id=" + fileId + "&dat=" + 
2433					cipher(buildXML(op, params), cipherKey);
2434
2435				try {
2436					httpRequest.send(requestData);
2437				}
2438				catch(e) {
2439					errorCallback("java.lang.IOException", "No se pudo conectar con el servidor remoto");
2440				}
2441			}
2442			
2443			/**
2444			 * Invoca un Intent con la operacion seleccionada, la configuraci\u00F3n establecida y las campos del
2445			 * formulario pasado como parametro. Si se define un callback para tratar el caso de exito o error de
2446			 * la operacion, se intentara descargar el resultado devuelto por la app del servidor intermedio de
2447			 * comunicacion. 
2448			 *
2449			 * @param intentURL URL para la invocacion del Cliente JavaScript
2450			 * @param idSession Identificador de la sesi\u00F3n para la recuperaci\u00F3n del resultado.
2451			 * @param cipherKey Clave de cifrado para la respuesta del servidor.
2452			 * @param successCallback Actuaci\u00F3n a realizar cuando se recupera el resultado de la operaci&oacute;n.
2453			 * @param errorCallback Actuaci\u00F3n a realizar cuando ocurre un error al recuperar el resultado.
2454			 */
2455			function execAppIntent (intentURL, idSession, cipherKey, successCallback, errorCallback) {
2456
2457				wrongInstallation = false;
2458			
2459				// Invocamos al cliente de firma movil.
2460				try {
2461					openUrl(intentURL, errorCallback);
2462				}
2463				catch (e) {
2464					//console.log("Error al abrir la aplicacion nativa: " + e);
2465					return;
2466				}
2467
2468				if (successCallback != null || errorCallback != null) {
2469					if (idSession != null && idSession != undefined && 
2470							((successCallback != undefined && successCallback != null) ||
2471									(errorCallback != undefined && errorCallback != null))) {
2472						getStoredFileFromServlet(idSession, retrieverServletAddress, cipherKey, successCallback, errorCallback);
2473					}
2474				}
2475			}
2476
2477			/**
2478			 * Construye una URL para la invocacion del Cliente @firma nativo.
2479			 * @param op Funcion a invocar en el cliente nativo.
2480			 * @param params Parametros para la configuracion de la operacion.
2481			 */
2482			function buildUrl (op, params) {
2483
2484				var urlParams = "";
2485				if (params != null && params != undefined) {
2486					for (var i = 0; i < params.length; i++) {
2487						if (params[i].value != null && params[i].value != "null") {
2488							urlParams += (i != 0 ? '&' : '') + params[i].key + '=' + encodeURIComponent(params[i].value); 
2489						}
2490					}
2491				}
2492
2493				// En el caso de Chrome en Android, construimos la URL en forma de Intent
2494				var url;
2495				if (isChrome() && isAndroid()) {
2496					url = 'intent://' + op + '?' + urlParams + "#Intent;scheme=afirma;package=es.gob.afirma;end";
2497				}
2498				else {
2499					url = 'afirma://' + op + '?' + urlParams;
2500				}
2501				return url;
2502			}
2503
2504			/**
2505			 * Construye un XML con lo valores del array de parametros proporcionado.
2506			 * @param op Operacion que configuramos
2507			 * @param params Array con los parametros del array. 
2508			 * @returns XML.
2509			 */
2510			function buildXML (op, params) {
2511				op = (op == null ? "op" : op);
2512				var xml = '<' + op +'>';
2513				if (params != null) {
2514					for (var i = 0; i < params.length; i++) {
2515						xml += '<e k="' + params[i].key + '" v="' + params[i].value + '"/>';
2516					}
2517				}
2518				return Base64.encode(xml + '</' + op + '>');
2519			}
2520
2521			/**
2522			 * Crea una URL a partir de los parametros introducidos para la invocaci&oacute;n de
2523			 * una app nativa para que descargue la configuracion de la operaci&oacute;n a realizar.
2524			 * @param op Codigo de la operacion a la que tiene que invocar la URL.
2525			 * @param id Identificador para la descarga.
2526			 * @param rtServlet Servlet para la descarga de la configuraci&oacute;n.
2527			 * @param cipherKey Clave para el descifrado.
2528			 * @returns URL para la llamada a la app con los datos necesarios para que descargue
2529			 * la configuraci&oacute;
2529n de la operaci&oacute;n a realizar.
2530			 */
2531			function buildUrlWithoutData (op, id, rtServlet, cipherKey) {
2532				var j = 0;
2533				var newParams = new Array();
2534				newParams[j++] = {key:"fileid", value:id};
2535				if (rtServlet != null || rtServlet != undefined) {
2536					newParams[j++] = {key:"rtservlet", value:rtServlet};
2537				}
2538				if (cipherKey != null || cipherKey != undefined) {
2539					newParams[j++] = {key:"key", value:cipherKey};
2540				}
2541				return buildUrl(op, newParams);
2542			};
2543
2544			/**
2545			 * Llama a la aplicacion de firma a traves de la URL de invocacion sin que afecte
2546			 * a la pagina que se esta mostrando.
2547			 * @param url URL de invocacion.
2548			 * @param errorCallback Funcion de error que deberia lanzarse (ademas de una excepcion),
2549			 * si no fuese posible abrir la URL. Puede ser nulo.
2550			 */
2551			function openUrl (url, errorCallback) {
2552				
2553				// Usamos el modo de invocacion mas apropiado segun el entorno
2554				if (isChrome() || isIOS()) {
2555					// Usamos document.location porque tiene mejor soporte por los navegadores que
2556					// window.location que es el mecanismo estandar
2557					document.location = url;
2558				}
2559				else {
2560
2561					// Si ya existe el iframe, lo eliminamos para despues volverlo a crear 
2562					if (document.getElementById("iframeAfirma") != null) {
2563						try {
2564							var element = document.getElementById("iframeAfirma");
2565							element.outerHTML = "";
2566							delete element;
2567						}
2568						catch (e) {
2569							// No hacemos nada
2570						}
2571					}
2572
2573					// En el caso de ser una version de internet Explorer que soportase la deteccion de aplicacion
2574					// capaces de manejar el protocolo, aprovechamos esta caracteristica (Internet Explorer para Windows 8 Modern UI)
2575
2576					if (navigator.msLaunchUri) {
2577						navigator.msLaunchUri(
2578								url,
2579								null,
2580								function() {
2581									// Bloqueamos la conexion para evitar que se sigan haciendo comprobaciones
2582									wrongInstallation = true;
2583								}
2584						);
2585					}
2586					else {
2587						// Abrimos la URL por medio de un iframe
2588						openUrlWithIframe(url);
2589					}
2590				}
2591			}
2592
2593			/**
2594			 * Llama a la aplicacion de firma por medio de un iframe.
2595			 * @param url URL de invocacion.
2596			 */
2597			function openUrlWithIframe (url) {
2598				var iframeElem = document.createElement("iframe");
2599
2600				var idAttr = document.createAttribute("id");
2601				idAttr.value = "iframeAfirma";
2602				iframeElem.setAttributeNode(idAttr);
2603
2604				var srcAttr = document.createAttribute("src");
2605				srcAttr.value = url;
2606				iframeElem.setAttributeNode(srcAttr);
2607
2608				var heightAttr = document.createAttribute("height");
2609				heightAttr.value = 1;
2610				iframeElem.setAttributeNode(heightAttr);
2611
2612				var widthAttr = document.createAttribute("width");
2613				widthAttr.value = 1;
2614				iframeElem.setAttributeNode(widthAttr);
2615
2616				document.body.appendChild(iframeElem);				
2617			}
2618				
2619			/**
2620			 * Ejecuta el metodo de error si el html recuperado es tal o el metodo de exito si no lo es,
2621			 * en cuyo caso previamente descifrara el resultado. 
2622			 * @param html Resultado obtenido.
2623			 * @param cipherKey Clave para el descifrado del resultado si no es un error.
2624			 * @param successCallback Metodo a ejecutar en caso de exito.
2625			 * @param errorCallback Metodo a ejecutar en caso de error.
2626			 * @returns Devuelve true si se ha fallado pero se puede volver a reintentar, false en caso de
2627			 * error determinante o exito.
2628			 */
2629			function successResponseFunction (html, cipherKey, successCallback, errorCallback) {
2630				
2631				// Si se obtiene el mensaje de  error de que el identificador no existe, seguimos intentandolo
2632				if (html.substr(0, 6).toLowerCase() == "err-06") {
2633					return true;
2634				}
2635
2636				// Si se obtiene otro mensaje de error, se deja de intentar y se ejecuta la funcion callback de error
2637				if (html.substr(0, 4).toLowerCase() == "err-" && html.indexOf(":=") != -1) {
2638					errorMessage = html.substring(html.indexOf(":=") + 2);
2639					errorType = "java.lang.Exception";
2640					errorCallback(errorType, errorMessage);
2641					return false;
2642				}
2643				
2644				// Se ha cancelado la operacion
2645				if (html == "CANCEL" || html == "CANCEL\r\n") {
2646					errorCallback("es.gob.afirma.core.AOCancelledOperationException", "Operacion cancelada por el usuario");
2647					return false;
2648				}
2649				
2650				// La operacion ha finalizado correctamene (Funcion de guardado)
2651				if (html == "OK" || html == "OK\r\n") {
2652					successCallback();
2653					return false;
2654				}
2655				
2656				// Se ha producido un error
2657				if (html.length > 4 && html.substr(0, 4) == "SAF_") {
2658					errorCallback("java.lang.Exception", html);
2659					return false;
2660				}
2661				
2662				// Si no se obtuvo un error, habremos recibido la firma y posiblemente el certificado (que antecederia a la
2663				// firma y se separaria de ella con '|'). Si se definio una clave de cifrado, consideramos que la firma
2664				// y el certificado (en caso de estar) llegan cifrados. El cifrado de ambos elementos es independiente
2665				var certificate;
2666				var signature;
2667				var sepPos = html.indexOf('|');
2668				if (sepPos == -1) {
2669					if (cipherKey != undefined && cipherKey != null) {
2670						signature = decipher(html, cipherKey);
2671					}
2672					else {
2673						signature = fromBase64UrlSaveToBase64(html);
2674					}
2675				}
2676				else {
2677					if (cipherKey != undefined && cipherKey != null) {
2678						certificate = decipher(html.substring(0, sepPos), cipherKey);
2679						signature = decipher(html.substring(sepPos + 1), cipherKey);
2680					}
2681					else {
2682						certificate = fromBase64UrlSaveToBase64(html.substring(0, sepPos));
2683						signature = fromBase64UrlSaveToBase64(html.substring(sepPos + 1));
2684					}
2685				}
2686
2687				successCallback(signature, certificate);
2688
2689				return false;
2690			}
2691
2692			function errorResponseFunction (type, message, errorCallback) {
2693
2694				errorType = (type != null && type.length > 0) ?
2695						type : "java.lang.Exception";
2696				errorMessage = (message != null && message.length > 0) ?
2697						message : "No se ha podido extablecer la comunicaci\u00F3n entre la aplicaci\u00F3n de firma y la p\u00E1gina web";
2698				if (errorCallback != undefined && errorCallback != null) {
2699					errorCallback(errorType, errorMessage);
2700				}
2701			}
2702
2703			var NUM_MAX_ITERATIONS = 15;
2704			var iterations = 0;
2705
2706			function getStoredFileFromServlet (idDocument, servletAddress, cipherKey, successCallback, errorCallback) {
2707
2708				var httpRequest = getHttpRequest();
2709				if (!httpRequest) {
2710					throwException("java.lang.Exception", "Su navegador no permite obtener el resulado de la operaci\u00F3n");
2711				}
2712
2713				iterations = 0;
2714				setTimeout(retrieveRequest, 4000, httpRequest, servletAddress, "op=get&v=1_0&id=" + idDocument + "&it=0", cipherKey, successCallback, errorCallback);
2715			}
2716
2717			function retrieveRequest(httpRequest, url, params, cipherKey, successCallback, errorCallback) {
2718
2719				if (wrongInstallation) {
2720					errorResponseFunction("es.gob.afirma.standalone.ApplicationNotFoundException", "AutoFirma no se encuentra instalado en el sistema.", errorCallback);
2721					return;
2722				}
2723			
2724				// Contamos la nueva llamada al servidor
2725				if (iterations > NUM_MAX_ITERATIONS) {
2726					errorResponseFunction("java.util.concurrent.TimeoutException", "El tiempo para la recepcion de la firma por la pagina web ha expirado", errorCallback);
2727					return;
2728				}
2729				iterations++;
2730
2731				httpRequest.onreadystatechange = function() {
2732					if (httpRequest.readyState == 4) {
2733						if (httpRequest.status == 200) {
2734							var needContinue = successResponseFunction(httpRequest.responseText, cipherKey, successCallback, errorCallback);
2735							if (needContinue) {
2736								setTimeout(retrieveRequest, 4000, httpRequest, url, params.replace("&it=" + (iterations-1), "&it=" + iterations), cipherKey, successCallback, errorCallback);
2737							}
2738						}
2739						else {
2740							errorResponseFunction(null, httpRequest.responseText, errorCallback);
2741						}
2742					}					
2743				}
2744				
2745				httpRequest.onerror = function() {
2746					errorResponseFunction("java.lang.Exception", "No se pudo conectar con el servidor intermedio para la recuperacion del resultado de la operacion", errorCallback);
2747				}
2748
2749				httpRequest.open("POST", url, true);
2750				httpRequest.setRequestHeader("Content-type","application/x-www-form-urlencoded");
2751				
2752				try {
2753					httpRequest.send(params);
2754				}
2755				catch(e) {
2756					// Error en la llamada para al recuperacion del resultado. No lo encuentra o problema
2757					// de tipo cross-domain
2758					errorResponseFunction("java.lang.IOException", "Ocurrio un error de red en la llamada al servicio de firma", errorCallback);
2759					return;
2760				}
2761			}
2762
2763			/**
2764			 * Realiza un descifrado DES compatible con Java (Algoritmo DES, modo CBC, sin Padding).
2765			 * Recibe en base 64 la cadena de texto cifrado antecedido por el padding anadido manualmente
2766			 * a los datos para permitir el cifrado DES (separado por un punto ('.')), ademas de la clave
2767			 * para descifrar.
2768			 * Como resultado devuelve la cadena de texto descifrada en base 64.
2769			 */
2770			function decipher(cipheredData, key) {
2771								
2772				var dotPos = cipheredData.indexOf('.');
2773				var padding = cipheredData.substr(0, dotPos);
2774				
2775				var deciphered = Cipher.des(key, Cipher.base64ToString(fromBase64UrlSaveToBase64(cipheredData.substr(dotPos + 1))), 0, 0, null);
2776				return Cipher.stringToBase64(deciphered.substr(0, deciphered.length - parseInt(padding) - 8));
2777			}
2778			
2779			/**
2780			 * Realiza un cifrado DES compatible con Java (Algoritmo DES, modo CBC, sin Padding).
2781			 * @param dataB64 Cadena de texto base 64.
2782			 * @param key Clave de cifrado.
2783			 * @return Base 64 cifrado.
2784			 */
2785			function cipher(dataB64, key) {
2786
2787				var data = Cipher.base64ToString(fromBase64UrlSaveToBase64(dataB64));
2788				var padding = (8 - (data.length % 8)) % 8;
2789
2790				// Los datos cifrados los pasamos a base 64 y, antes de devolverlos le anteponemos el padding que
2791				// le habra agregado el metodo de cifrado separados por un punto ('.').
2792				return padding  + "." + Cipher.stringToBase64(Cipher.des(key, data, 1, 0, null)).replace(/\+/g, "-").replace(/\//g, "_");
2793			}
2794
2795			/**
2796			 * Convierte de Base64 URL Save a Base64 normal.
2797			 */
2798			function fromBase64UrlSaveToBase64(base64UrlSave) {
2799				if (!base64UrlSave) {
2800					return base64UrlSave;
2801				}
2802				return base64UrlSave.replace(/\-/g, "+").replace(/\_/g, "/")
2803			}
2804
2805			/* Metodos que publicamos del objeto AppAfirmaJS */
2806			return {
2807				echo : echo,
2808				checkTime : checkTime,
2809				setKeyStore : setKeyStore,
2810				sign : sign,
2811				coSign : coSign,
2812				counterSign : counterSign,
2813				signBatch : signBatch,
2814				selectCertificate : selectCertificate,
2815				saveDataToFile : saveDataToFile,
2816				signAndSaveToFile : signAndSaveToFile,
2817				getFileNameContentBase64: getFileNameContentBase64,
2818				getMultiFileNameContentBase64 : getMultiFileNameContentBase64,
2819				getBase64FromText : getBase64FromText,
2820				getTextFromBase64 : getTextFromBase64,
2821				setServlets : setServlets,
2822				setStickySignatory : setStickySignatory,
2823				setLocale : setLocale,
2824				getErrorMessage : getErrorMessage,
2825				getErrorType : getErrorType,
2826				getCurrentLog : getCurrentLog
2827			}
2828		});
2829
2830		/* Metodos que publicamos del objeto MiniApplet */
2831		return {
2832			
2833			VERSION : VERSION,
2834			
2835			/* Publicamos las variables para la comprobacion de hora. */		
2836			CHECKTIME_NO : CHECKTIME_NO,
2837			CHECKTIME_RECOMMENDED : CHECKTIME_RECOMMENDED,
2838			CHECKTIME_OBLIGATORY : CHECKTIME_OBLIGATORY,
2839			
2840			/* Publicamos las variables para establecer parametros personalizados en la JVM */
2841			JAVA_ARGUMENTS : JAVA_ARGUMENTS,
2842			SYSTEM_PROPERTIES : SYSTEM_PROPERTIES,
2843			
2844			/* Publicamos las variables para configurar un almacen de certificados concreto. */		
2845			KEYSTORE_WINDOWS : KEYSTORE_WINDOWS,
2846			KEYSTORE_APPLE : KEYSTORE_APPLE,
2847			KEYSTORE_PKCS12 : KEYSTORE_PKCS12,
2848			KEYSTORE_PKCS11 : KEYSTORE_PKCS11,
2849			KEYSTORE_MOZILLA : KEYSTORE_MOZILLA,
2850			KEYSTORE_SHARED_NSS : KEYSTORE_SHARED_NSS,
2851			KEYSTORE_JAVA : KEYSTORE_JAVA,
2852			KEYSTORE_JCEKS : KEYSTORE_JCEKS,
2853			KEYSTORE_JAVACE : KEYSTORE_JAVACE,
2854			KEYSTORE_DNIE : KEYSTORE_DNIE,
2855
2856			/* Constantes para la deteccion de la aplicacion de autofirma */
2857			AUTOFIRMA_LAUNCHING_TIME : AUTOFIRMA_LAUNCHING_TIME,
2858			AUTOFIRMA_CONNECTION_RETRIES : AUTOFIRMA_CONNECTION_RETRIES,
2859
2860			/* Variable para forzar el uso del mecanismo de comunicacion por servidor intermedio */
2861			setForceWSMode : setForceWSMode,
2862
2863			/* Metodos visibles. */
2864			cargarMiniApplet : cargarMiniApplet,
2865			cargarAppAfirma : cargarAppAfirma,
2866			echo : echo,
2867			checkTime : checkTime,
2868			sign : sign,
2869			coSign : coSign,
2870			counterSign : counterSign,
2871			signBatch : signBatch,
2872			selectCertificate : selectCertificate,
2873			saveDataToFile : saveDataToFile,
2874			signAndSaveToFile : signAndSaveToFile,
2875			getFileNameContentBase64: getFileNameContentBase64,
2876			getMultiFileNameContentBase64 : getMultiFileNameContentBase64,
2877			getBase64FromText : getBase64FromText,
2878			getTextFromBase64 : getTextFromBase64,
2879			downloadRemoteData : downloadRemoteData,
2880			setKeyStore : setKeyStore,
2881			setServlets : setServlets,
2882			setStickySignatory : setStickySignatory,
2883			setLocale : setLocale,
2884			getErrorMessage : getErrorMessage,
2885			getErrorType : getErrorType,
2886			getCurrentLog : getCurrentLog,
2887			isAndroid : isAndroid,
2888			isIOS : isIOS
2889		};
2890})(window, undefined);
2891
2892
2893
2894
2895
2896/**
2897 *  Base64 encode / decode
2898 *  http://www.webtoolkit.info/
2899 */
2900var Base64 = {
2901
2902		// private property
2903		_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
2904		_keyStr_URL_SAFE : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_=",
2905
2906		// public method for encoding
2907		encode : function (input, URL_SAFE) {
2908			
2909			var keyStr = (URL_SAFE == true) ? this._keyStr_URL_SAFE : this._keyStr;
2910			
2911			var output = "";
2912			var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
2913			var i = 0;
2914
2915			input = Base64._utf8_encode(input);
2916
2917			while (i < input.length) {
2918
2919				chr1 = input.charCodeAt(i++);
2920				chr2 = input.charCodeAt(i++);
2921				chr3 = input.charCodeAt(i++);
2922
2923				enc1 = chr1 >> 2;
2924				enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
2925				enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
2926				enc4 = chr3 & 63;
2927
2928				if (isNaN(chr2)) {
2929					enc3 = enc4 = 64;
2930				} else if (isNaN(chr3)) {
2931					enc4 = 64;
2932				}
2933
2934				output = output +
2935				keyStr.charAt(enc1) + keyStr.charAt(enc2) +
2936				keyStr.charAt(enc3) + keyStr.charAt(enc4);
2937
2938			}
2939
2940			return output;
2941		},
2942
2943		/**
2944		 * Codifica los datos binarios obtenidos en un ArrayBuffer.
2945		 */
2946		encodeArrayBuffer : function(input, URL_SAFE) {
2947			
2948			var uInt8Array = new Uint8Array(input);
2949		    var i = uInt8Array.length;
2950		    var binaryString = new Array(i);
2951		    while (i--)
2952		    {
2953		      binaryString[i] = String.fromCharCode(uInt8Array[i]);
2954		    }
2955
2956		    return URL_SAFE ?
2957		    		window.btoa(binaryString.join('')).replace('+', '-').replace('/', '_') :
2958		    			window.btoa(binaryString.join(''));
2959		},
2960		
2961		/**
2962		 * Codifica un array de bytes a Base64.
2963		 *
2964		 * @param {Array.<number>|Uint8Array} input An array of bytes (numbers with
2965		 *     value in [0, 255]) to encode.
2966		 * @param {boolean=} opt_webSafe Boolean indicating we should use the
2967		 *     alternative alphabet.
2968		 * @return {string} The base64 encoded string.
2969		 */
2970		encodeByteArray : function(input, URL_SAFE) {
2971
2972		  var keyStr = (URL_SAFE == true) ? this._keyStr_URL_SAFE : this._keyStr;
2973
2974		  var output = [];
2975
2976		  for (var i = 0; i < input.length; i += 3) {
2977		    var byte1 = input[i];
2978		    var haveByte2 = i + 1 < input.length;
2979		    var byte2 = haveByte2 ? input[i + 1] : 0;
2980		    var haveByte3 = i + 2 < input.length;
2981		    var byte3 = haveByte3 ? input[i + 2] : 0;
2982
2983		    var outByte1 = byte1 >> 2;
2984		    var outByte2 = ((byte1 & 0x03) << 4) | (byte2 >> 4);
2985		    var outByte3 = ((byte2 & 0x0F) << 2) | (byte3 >> 6);
2986		    var outByte4 = byte3 & 0x3F;
2987
2988		    if (!haveByte3) {
2989		      outByte4 = 64;
2990
2991		      if (!haveByte2) {
2992		        outByte3 = 64;
2993		      }
2994		    }
2995
2996		    output.push(keyStr.charAt(outByte1),
2997		    			keyStr.charAt(outByte2),
2998		    			keyStr.charAt(outByte3),
2999		    			keyStr.charAt(outByte4));
3000		  }
3001
3002		  return output.join('');
3003		},
3004		
3005		// public method for decoding
3006		decode : function (input, URL_SAFE) {
3007			
3008			var keyStr = (URL_SAFE == true) ? this._keyStr_URL_SAFE : this._keyStr;
3009			
3010			var output = "";
3011			var chr1, chr2, chr3;
3012			var enc1, enc2, enc3, enc4;
3013			var i = 0;
3014
3015			input = (URL_SAFE == true) ? input.replace(/[^A-Za-z0-9\-\_\=]/g, "") : input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
3016
3017			while (i < input.length) {
3018
3019				enc1 = keyStr.indexOf(input.charAt(i++));
3020				enc2 = keyStr.indexOf(input.charAt(i++));
3021				enc3 = keyStr.indexOf(input.charAt(i++));
3022				enc4 = keyStr.indexOf(input.charAt(i++));
3023
3024				chr1 = (enc1 << 2) | (enc2 >> 4);
3025				chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
3026				chr3 = ((enc3 & 3) << 6) | enc4;
3027
3028				output = output + String.fromCharCode(chr1);
3029
3030				if (enc3 != 64) {
3031					output = output + String.fromCharCode(chr2);
3032				}
3033				if (enc4 != 64) {
3034					output = output + String.fromCharCode(chr3);
3035				}
3036
3037			}
3038
3039			output = Base64._utf8_decode(output);
3040
3041			return output;
3042
3043		},
3044
3045		// private method for UTF-8 encoding
3046		_utf8_encode : function (string) {
3047			string = string.replace(/\r\n/g,"\n");
3048			var utftext = "";
3049
3050			for (var n = 0; n < string.length; n++) {
3051
3052				var c = string.charCodeAt(n);
3053
3054				if (c < 128) {
3055					utftext += String.fromCharCode(c);
3056				}
3057				else if((c > 127) && (c < 2048)) {
3058					utftext += String.fromCharCode((c >> 6) | 192);
3059					utftext += String.fromCharCode((c & 63) | 128);
3060				}
3061				else {
3062					utftext += String.fromCharCode((c >> 12) | 224);
3063					utftext += String.fromCharCode(((c >> 6) & 63) | 128);
3064					utftext += String.fromCharCode((c & 63) | 128);
3065				}
3066
3067			}
3068
3069			return utftext;
3070		},
3071
3072		// private method for UTF-8 decoding
3073		_utf8_decode : function (utftext) {
3074			var string = "";
3075			var i = 0;
3076			var c = 0;
3077			var c1 = 0;
3078			var c2 = 0;
3079
3080			while ( i < utftext.length ) {
3081
3082				c = utftext.charCodeAt(i);
3083
3084				if (c < 128) {
3085					string += String.fromCharCode(c);
3086					i++;
3087				}
3088				else if((c > 191) && (c < 224)) {
3089					c2 = utftext.charCodeAt(i+1);
3090					string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
3091					i += 2;
3092				}
3093				else {
3094					c2 = utftext.charCodeAt(i+1);
3095					var c3 = utftext.charCodeAt(i+2);
3096					string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
3097					i += 3;
3098				}
3099			}
3100
3101			return string;
3102		}
3103};
3104
3105//Paul Tero, July 2001
3106//http://www.tero.co.uk/des/
3107
3108//Optimised for performance with large blocks by Michael Hayworth, November 2001
3109//http://www.netdealing.com
3110
3111//THIS SOFTWARE IS PROVIDED "AS IS" AND
3112//ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
3113//IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
3114//ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
3115//FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
3116//DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
3117//OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
3118//HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
3119//LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
3120//OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
3121//SUCH DAMAGE.
3122
3123//des
3124//this takes the key, the message, and whether to encrypt or decrypt
3125var Cipher = {
3126	tableStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
3126+/",
3127	tableStr_URL_SAFE : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
3128	
3129	des : function  (key, message, encrypt, mode, iv, padding) {
3130		//declaring this locally speeds things up a bit
3131		var spfunction1 = new Array (0x1010400,0,0x10000,0x1010404,0x1010004,0x10404,0x4,0x10000,0x400,0x1010400,0x1010404,0x400,0x1000404,0x1010004,0x1000000,0x4,0x404,0x1000400,0x1000400,0x10400,0x10400,0x1010000,0x1010000,0x1000404,0x10004,0x1000004,0x1000004,0x10004,0,0x404,0x10404,0x1000000,0x10000,0x1010404,0x4,0x1010000,0x1010400,0x1000000,0x1000000,0x400,0x1010004,0x10000,0x10400,0x1000004,0x400,0x4,0x1000404,0x10404,0x1010404,0x10004,0x1010000,0x1000404,0x1000004,0x404,0x10404,0x1010400,0x404,0x1000400,0x1000400,0,0x10004,0x10400,0,0x1010004);
3132		var spfunction2 = new Array (-0x7fef7fe0,-0x7fff8000,0x8000,0x108020,0x100000,0x20,-0x7fefffe0,-0x7fff7fe0,-0x7fffffe0,-0x7fef7fe0,-0x7fef8000,-0x80000000,-0x7fff8000,0x100000,0x20,-0x7fefffe0,0x108000,0x100020,-0x7fff7fe0,0,-0x80000000,0x8000,0x108020,-0x7ff00000,0x100020,-0x7fffffe0,0,0x108000,0x8020,-0x7fef8000,-0x7ff00000,0x8020,0,0x108020,-0x7fefffe0,0x100000,-0x7fff7fe0,-0x7ff00000,-0x7fef8000,0x8000,-0x7ff00000,-0x7fff8000,0x20,-0x7fef7fe0,0x108020,0x20,0x8000,-0x80000000,0x8020,-0x7fef8000,0x100000,-0x7fffffe0,0x100020,-0x7fff7fe0,-0x7fffffe0,0x100020,0x108000,0,-0x7fff8000,0x8020,-0x80000000,-0x7fefffe0,-0x7fef7fe0,0x108000);
3133		var spfunction3 = new Array (0x208,0x8020200,0,0x8020008,0x8000200,0,0x20208,0x8000200,0x20008,0x8000008,0x8000008,0x20000,0x8020208,0x20008,0x8020000,0x208,0x8000000,0x8,0x8020200,0x200,0x20200,0x8020000,0x8020008,0x20208,0x8000208,0x20200,0x20000,0x8000208,0x8,0x8020208,0x200,0x8000000,0x8020200,0x8000000,0x20008,0x208,0x20000,0x8020200,0x8000200,0,0x200,0x20008,0x8020208,0x8000200,0x8000008,0x200,0,0x8020008,0x8000208,0x20000,0x8000000,0x8020208,0x8,0x20208,0x20200,0x8000008,0x8020000,0x8000208,0x208,0x8020000,0x20208,0x8,0x8020008,0x20200);
3134		var spfunction4 = new Array (0x802001,0x2081,0x2081,0x80,0x802080,0x800081,0x800001,0x2001,0,0x802000,0x802000,0x802081,0x81,0,0x800080,0x800001,0x1,0x2000,0x800000,0x802001,0x80,0x800000,0x2001,0x2080,0x800081,0x1,0x2080,0x800080,0x2000,0x802080,0x802081,0x81,0x800080,0x800001,0x802000,0x802081,0x81,0,0,0x802000,0x2080,0x800080,0x800081,0x1,0x802001,0x2081,0x2081,0x80,0x802081,0x81,0x1,0x2000,0x800001,0x2001,0x802080,0x800081,0x2001,0x2080,0x800000,0x802001,0x80,0x800000,0x2000,0x802080);
3135		var spfunction5 = new Array (0x100,0x2080100,0x2080000,0x42000100,0x80000,0x100,0x40000000,0x2080000,0x40080100,0x80000,0x2000100,0x40080100,0x42000100,0x42080000,0x80100,0x40000000,0x2000000,0x40080000,0x40080000,0,0x40000100,
31350x42080100,0x42080100,0x2000100,0x42080000,0x40000100,0,0x42000000,0x2080100,0x2000000,0x42000000,0x80100,0x80000,0x42000100,0x100,0x2000000,0x40000000,0x2080000,0x42000100,0x40080100,0x2000100,0x40000000,0x42080000,0x2080100,0x40080100,0x100,0x2000000,0x42080000,0x42080100,0x80100,0x42000000,0x42080100,0x2080000,0,0x40080000,0x42000000,0x80100,0x2000100,0x40000100,0x80000,0,0x40080000,0x2080100,0x40000100);
3136		var spfunction6 = new Array (0x20000010,0x20400000,0x4000,0x20404010,0x20400000,0x10,0x20404010,0x400000,0x20004000,0x404010,0x400000,0x20000010,0x400010,0x20004000,0x20000000,0x4010,0,0x400010,0x20004010,0x4000,0x404000,0x20004010,0x10,0x20400010,0x20400010,0,0x404010,0x20404000,0x4010,0x404000,0x20404000,0x20000000,0x20004000,0x10,0x20400010,0x404000,0x20404010,0x400000,0x4010,0x20000010,0x400000,0x20004000,0x20000000,0x4010,0x20000010,0x20404010,0x404000,0x20400000,0x404010,0x20404000,0,0x20400010,0x10,0x4000,0x20400000,0x404010,0x4000,0x400010,0x20004010,0,0x20404000,0x20000000,0x400010,0x20004010);
3137		var spfunction7 = new Array (0x200000,0x4200002,0x4000802,0,0x800,0x4000802,0x200802,0x4200800,0x4200802,0x200000,0,0x4000002,0x2,0x4000000,0x4200002,0x802,0x4000800,0x200802,0x200002,0x4000800,0x4000002,0x4200000,0x4200800,0x200002,0x4200000,0x800,0x802,0x4200802,0x200800,0x2,0x4000000,0x200800,0x4000000,0x200800,0x200000,0x4000802,0x4000802,0x4200002,0x4200002,0x2,0x200002,0x4000000,0x4000800,0x200000,0x4200800,0x802,0x200802,0x4200800,0x802,0x4000002,0x4200802,0x4200000,0x200800,0,0x2,0x4200802,0,0x200802,0x4200000,0x800,0x4000002,0x4000800,0x800,0x200002);
3138		var spfunction8 = new Array (0x10001040,0x1000,0x40000,0x10041040,0x10000000,0x10001040,0x40,0x10000000,0x40040,0x10040000,0x10041040,0x41000,0x10041000,0x41040,0x1000,0x40,0x10040000,0x10000040,0x10001000,0x1040,0x41000,0x40040,0x10040040,0x10041000,0x1040,0,0,0x10040040,0x10000040,0x10001000,0x41040,0x40000,0x41040,0x40000,0x10041000,0x1000,0x40,0x10040040,0x1000,0x41040,0x10001000,0x40,0x10000040,0x10040000,0x10040040,0x10000000,0x40000,0x10001040,0,0x10041040,0x40040,0x10000040,0x10040000,0x10001000,0x10001040,0,0x10041040,0x41000,0x41000,0x1040,0x1040,0x40040,0x10000000,0x10041000);
3139
3140		//create the 16 or 48 subkeys we will need
3141		var keys = Cipher.des_createKeys (key);
3142		var m=0, i, j, temp, right1, right2, left, right, looping;
3143		var cbcleft, cbcleft2, cbcright, cbcright2;
3144		var endloop, loopinc;
3145		var len = message.length;
3146		var chunk = 0;
3147		//set up the loops for single and triple des
3148		var iterations = keys.length == 32 ? 3 : 9; //single or triple des
3149		if (iterations == 3) {looping = encrypt ? new Array (0, 32, 2) : new Array (30, -2, -2);}
3150		else {looping = encrypt ? new Array (0, 32, 2, 62, 30, -2, 64, 96, 2) : new Array (94, 62, -2, 32, 64, 2, 30, -2, -2);}
3151
3152		//pad the message depending on the padding parameter
3153		if (padding == 2) message += "        "; //pad the message with spaces
3154		else if (padding == 1) {temp = 8-(len%8); message += String.fromCharCode (temp,temp,temp,temp,temp,temp,temp,temp); if (temp==8) len+=8;} //PKCS7 padding
3155		else if (!padding) message += "\0\0\0\0\0\0\0\0"; //pad the message out with null bytes
3156
3157		//store the result here
3158		var result = "";
3159		var tempresult = "";
3160
3161		if (mode == 1) { //CBC mode
3162			cbcleft = (iv.charCodeAt(m++) << 24) | (iv.charCodeAt(m++) << 16) | (iv.charCodeAt(m++) << 8) | iv.charCodeAt(m++);
3163			cbcright = (iv.charCodeAt(m++) << 24) | (iv.charCodeAt(m++) << 16) | (iv.charCodeAt(m++) << 8) | iv.charCodeAt(m++);
3164			m=0;
3165		}
3166
3167		 //loop through each 64 bit chunk of the message
3168		while (m < len) {
3169			left = (message.charCodeAt(m++) << 24) | (message.charCodeAt(m++) << 16) | (message.charCodeAt(m++) << 8) | message.charCodeAt(m++);
3170			right = (message.charCodeAt(m++) << 24) | (message.charCodeAt(m++) << 16) | (message.charCodeAt(m++) << 8) | message.charCodeAt(m++);
3171
3172			//for Cipher Block Chaining mode, xor the message with the previous result
3173			if (mode == 1) {if (encrypt) {left ^= cbcleft; right ^= cbcright;} else {cbcleft2 = cbcleft; cbcright2 = cbcright; cbcleft = left; cbcright = right;}}
3174
3175			//first each 64 but chunk of the message must be permuted according to IP
3176			temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4);
3177			temp = ((left >>> 16) ^ right) & 0x0000ffff; right ^= temp; left ^= (temp << 16);
3178			temp = ((right >>> 2) ^ left) & 0x33333333; left ^= temp; right ^= (temp << 2);
3179			temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8);
3180			temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
3181
3182			left = ((left << 1) | (left >>> 31)); 
3183			right = ((right << 1) | (right >>> 31)); 
3184
3185			//do this either 1 or 3 times for each chunk of the message
3186			for (j=0; j<iterations; j+=3) {
3187				endloop = looping[j+1];
3188				loopinc = looping[j+2];
3189				//now go through and perform the encryption or decryption  
3190				for (i=looping[j]; i!=endloop; i+=loopinc) { //for efficiency
3191					right1 = right ^ keys[i]; 
3192					right2 = ((right >>> 4) | (right << 28)) ^ keys[i+1];
3193					//the result is attained by passing these bytes through the S selection functions
3194					temp = left;
3195					left = right;
3196					right = temp ^ (spfunction2[(right1 >>> 24) & 0x3f] | spfunction4[(right1 >>> 16) & 0x3f]
3197						  | spfunction6[(right1 >>>  8) & 0x3f] | spfunction8[right1 & 0x3f]
3198						  | spfunction1[(right2 >>> 24) & 0x3f] | spfunction3[(right2 >>> 16) & 0x3f]
3199						  | spfunction5[(right2 >>>  8) & 0x3f] | spfunction7[right2 & 0x3f]);
3200				}
3201				temp = left; left = right; right = temp; //unreverse left and right
3202			} //for either 1 or 3 iterations
3203
3204			//move then each one bit to the right
3205			left = ((left >>> 1) | (left << 31)); 
3206			right = ((right >>> 1) | (right << 31)); 
3207
3208			//now perform IP-1, which is IP in the opposite direction
3209			temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
3210			temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8);
3211			temp = ((right >>> 2) ^ left) & 0x33333333; left ^= temp; right ^= (temp << 2);
3212			temp = ((left >>> 16) ^ right) & 0x0000ffff; right ^= temp; left ^= (temp << 16);
3213			temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4);
3214
3215			//for Cipher Block Chaining mode, xor the message with the previous result
3216			if (mode == 1) {if (encrypt) {cbcleft = left; cbcright = right;} else {left ^= cbcleft2; right ^= cbcright2;}}
3217			tempresult += String.fromCharCode ((left>>>24), ((left>>>16) & 0xff), ((left>>>8) & 0xff), (left & 0xff), (right>>>24), ((right>>>16) & 0xff), ((right>>>8) & 0xff), (right & 0xff));
3218
3219			chunk += 8;
3220			if (chunk == 512) {result += tempresult; tempresult = ""; chunk = 0;}
3221		  } //for every 8 characters, or 64 bits in the message
3222
3223		  //return the result as an array
3224		  return result + tempresult;
3225	},
3226
3227	//this takes as input a 64 bit key (even though only 56 bits are used)
3228	//as an array of 2 integers, and returns 16 48 bit keys
3229	des_createKeys : function (key) {
3230		//declaring this locally speeds things up a bit
3231		var pc2bytes0  = new Array (0,0x4,0x20000000,0x20000004,0x10000,0x10004,0
3231x20010000,0x20010004,0x200,0x204,0x20000200,0x20000204,0x10200,0x10204,0x20010200,0x20010204);
3232		var pc2bytes1  = new Array (0,0x1,0x100000,0x100001,0x4000000,0x4000001,0x4100000,0x4100001,0x100,0x101,0x100100,0x100101,0x4000100,0x4000101,0x4100100,0x4100101);
3233		var pc2bytes2  = new Array (0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808,0,0x8,0x800,0x808,0x1000000,0x1000008,0x1000800,0x1000808);
3234		var pc2bytes3  = new Array (0,0x200000,0x8000000,0x8200000,0x2000,0x202000,0x8002000,0x8202000,0x20000,0x220000,0x8020000,0x8220000,0x22000,0x222000,0x8022000,0x8222000);
3235		var pc2bytes4  = new Array (0,0x40000,0x10,0x40010,0,0x40000,0x10,0x40010,0x1000,0x41000,0x1010,0x41010,0x1000,0x41000,0x1010,0x41010);
3236		var pc2bytes5  = new Array (0,0x400,0x20,0x420,0,0x400,0x20,0x420,0x2000000,0x2000400,0x2000020,0x2000420,0x2000000,0x2000400,0x2000020,0x2000420);
3237		var pc2bytes6  = new Array (0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002,0,0x10000000,0x80000,0x10080000,0x2,0x10000002,0x80002,0x10080002);
3238		var pc2bytes7  = new Array (0,0x10000,0x800,0x10800,0x20000000,0x20010000,0x20000800,0x20010800,0x20000,0x30000,0x20800,0x30800,0x20020000,0x20030000,0x20020800,0x20030800);
3239		var pc2bytes8  = new Array (0,0x40000,0,0x40000,0x2,0x40002,0x2,0x40002,0x2000000,0x2040000,0x2000000,0x2040000,0x2000002,0x2040002,0x2000002,0x2040002);
3240		var pc2bytes9  = new Array (0,0x10000000,0x8,0x10000008,0,0x10000000,0x8,0x10000008,0x400,0x10000400,0x408,0x10000408,0x400,0x10000400,0x408,0x10000408);
3241		var pc2bytes10 = new Array (0,0x20,0,0x20,0x100000,0x100020,0x100000,0x100020,0x2000,0x2020,0x2000,0x2020,0x102000,0x102020,0x102000,0x102020);
3242		var pc2bytes11 = new Array (0,0x1000000,0x200,0x1000200,0x200000,0x1200000,0x200200,0x1200200,0x4000000,0x5000000,0x4000200,0x5000200,0x4200000,0x5200000,0x4200200,0x5200200);
3243		var pc2bytes12 = new Array (0,0x1000,0x8000000,0x8001000,0x80000,0x81000,0x8080000,0x8081000,0x10,0x1010,0x8000010,0x8001010,0x80010,0x81010,0x8080010,0x8081010);
3244		var pc2bytes13 = new Array (0,0x4,0x100,0x104,0,0x4,0x100,0x104,0x1,0x5,0x101,0x105,0x1,0x5,0x101,0x105);
3245
3246		//how many iterations (1 for des, 3 for triple des)
3247		var iterations = key.length > 8 ? 3 : 1; //changed by Paul 16/6/2007 to use Triple DES for 9+ byte keys
3248		//stores the return keys
3249		var keys = new Array (32 * iterations);
3250		//now define the left shifts which need to be done
3251		var shifts = new Array (0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0);
3252		//other variables
3253		var lefttemp, righttemp, m=0, n=0, temp;
3254
3255		for (var j=0; j<iterations; j++) { //either 1 or 3 iterations
3256			var left = (key.charCodeAt(m++) << 24) | (key.charCodeAt(m++) << 16) | (key.charCodeAt(m++) << 8) | key.charCo
3256deAt(m++);
3257			var right = (key.charCodeAt(m++) << 24) | (key.charCodeAt(m++) << 16) | (key.charCodeAt(m++) << 8) | key.charCodeAt(m++);
3258
3259			temp = ((left >>> 4) ^ right) & 0x0f0f0f0f; right ^= temp; left ^= (temp << 4);
3260			temp = ((right >>> -16) ^ left) & 0x0000ffff; left ^= temp; right ^= (temp << -16);
3261			temp = ((left >>> 2) ^ right) & 0x33333333; right ^= temp; left ^= (temp << 2);
3262			temp = ((right >>> -16) ^ left) & 0x0000ffff; left ^= temp; right ^= (temp << -16);
3263			temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
3264			temp = ((right >>> 8) ^ left) & 0x00ff00ff; left ^= temp; right ^= (temp << 8);
3265			temp = ((left >>> 1) ^ right) & 0x55555555; right ^= temp; left ^= (temp << 1);
3266
3267			//the right side needs to be shifted and to get the last four bits of the left side
3268			temp = (left << 8) | ((right >>> 20) & 0x000000f0);
3269			//left needs to be put upside down
3270			left = (right << 24) | ((right << 8) & 0xff0000) | ((right >>> 8) & 0xff00) | ((right >>> 24) & 0xf0);
3271			right = temp;
3272
3273			//now go through and perform these shifts on the left and right keys
3274			for (var i=0; i < shifts.length; i++) {
3275				//shift the keys either one or two bits to the left
3276				if (shifts[i]) {left = (left << 2) | (left >>> 26); right = (right << 2) | (right >>> 26);}
3277				else {left = (left << 1) | (left >>> 27); right = (right << 1) | (right >>> 27);}
3278				left &= -0xf; right &= -0xf;
3279
3280				//now apply PC-2, in such a way that E is easier when encrypting or decrypting
3281				//this conversion will look like PC-2 except only the last 6 bits of each byte are used
3282				//rather than 48 consecutive bits and the order of lines will be according to 
3283				//how the S selection functions will be applied: S2, S4, S6, S8, S1, S3, S5, S7
3284				lefttemp = pc2bytes0[left >>> 28] | pc2bytes1[(left >>> 24) & 0xf]
3285					  | pc2bytes2[(left >>> 20) & 0xf] | pc2bytes3[(left >>> 16) & 0xf]
3286					  | pc2bytes4[(left >>> 12) & 0xf] | pc2bytes5[(left >>> 8) & 0xf]
3287					  | pc2bytes6[(left >>> 4) & 0xf];
3288				righttemp = pc2bytes7[right >>> 28] | pc2bytes8[(right >>> 24) & 0xf]
3289						| pc2bytes9[(right >>> 20) & 0xf] | pc2bytes10[(right >>> 16) & 0xf]
3290						| pc2bytes11[(right >>> 12) & 0xf] | pc2bytes12[(right >>> 8) & 0xf]
3291						| pc2bytes13[(right >>> 4) & 0xf];
3292				temp = ((righttemp >>> 16) ^ lefttemp) & 0x0000ffff; 
3293				keys[n++] = lefttemp ^ temp; keys[n++] = righttemp ^ (temp << 16);
3294			}
3295		} //for each iterations
3296		  //return the keys we've created
3297		return keys;
3298	},
3299
3300	//Convierte una cadena a Base 64. Debido a un error en el algoritmo original, pasaremos
3301	// de cadena a hexadecimal y de hexadecimal a Base64
3302	stringToBase64 : function  (s) {
3303
3304		// Para realizar la transformacion, primero debemos convertir a hexadecimal y luego a cadena
3305		
3306		// A hexadecimal
3307		var str = "";
3308		var hexes = new Array ("0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f");
3309		for (var i=0; i<s.length; i++) {str += hexes [s.charCodeAt(i) >> 4] + hexes [s.charCodeAt(i) & 0xf];}
3310		
3311		// A cadena
3312		var byteString;
3313		var byteArray = str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" ");
3314		
3315		// Vaciamos la cadena por liberar recursos
3316		str = "";
3317		
3318		try {
3319			byteString = String.fromCharCode.apply(null, byteArray);
3320		} catch (e) {
3321			var byteString = "";
3322			for (var i = 0, len = byteArray.length; i < len; i++) {
3323				byteString += String.fromCharCode(byteArray[i]);
3324			}
3325		}
3326
3327		return Cipher.btoa(byteString, null);
3328	},
3329
3330	//Convert a base64 string into a normal string
3331	base64ToString : function(s) {
3332		//the base 64 characters
3333		var BASE64 = new Array ('A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f','g','h','
3333i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/');
3334			
3335		var decode = new Object();
3336		for (var i=0; i<BASE64.length; i++) {decode[BASE64[i]] = i;} //inverse of the array
3337		decode['='] = 0; //add the equals sign as well
3338		var r = "", c1, c2, c3, c4, len=s.length; //define variables
3339		s += "===="; //just to make sure it is padded correctly
3340		for (var i=0; i<len; i+=4) { //4 input characters at a time
3341			c1 = s.charAt(i); //the 1st base64 input characther
3342			c2 = s.charAt(i+1);
3343			c3 = s.charAt(i+2);
3344			c4 = s.charAt(i+3);
3345			r += String.fromCharCode (((decode[c1] << 2) & 0xff) | (decode[c2] >> 4)); //reform the string
3346			if (c3 != '=') r += String.fromCharCode (((decode[c2] << 4) & 0xff) | (decode[c3] >> 2));
3347			if (c4 != '=') r += String.fromCharCode (((decode[c3] << 6) & 0xff) | decode[c4]);
3348		}
3349		return r;
3350	},
3351
3352	// --- Funciones para pasar de Hexadecimal a base64
3353
3354	btoa : function  (bin, urlSafe) {
3355		var table = (urlSafe == true ? Cipher.tableStr_URL_SAFE : Cipher.tableStr).split("");
3356			
3357		for (var i = 0, j = 0, len = bin.length / 3, base64 = []; i < len; ++i) {
3358			var a = bin.charCodeAt(j++), b = bin.charCodeAt(j++), c = bin.charCodeAt(j++);
3359			if ((a | b | c) > 255) throw new Error("String contains an invalid character");
3360			base64[base64.length] = table[a >> 2] + table[((a << 4) & 63) | (b >> 4)] +
3361			(isNaN(b) ? "=" : table[((b << 2) & 63) | (c >> 6)]) +
3362			(isNaN(b + c) ? "=" : table[c & 63]);
3363		}
3364		return base64.join("");
3365	}
3366};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.