diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..516f2de --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +*~ +.idea +*.iml diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..a194214 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright (c) 2014 Pierre Laperdrix + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + diff --git a/OSData.as b/OSData.as new file mode 100644 index 0000000..6443727 --- /dev/null +++ b/OSData.as @@ -0,0 +1,57 @@ +// OSData.as +package { + + import flash.display.Sprite; + import flash.text.Font; + import flash.text.FontType; + import flash.text.FontStyle; + import flash.external.ExternalInterface; + import flash.system.Capabilities; + + public class OSData extends Sprite + { + public function OSData() + { + ExternalInterface.addCallback('getFonts', this.getDeviceFonts); + ExternalInterface.addCallback('getOS', this.getOS); + ExternalInterface.addCallback('getResolution', this.getResolution); + ExternalInterface.addCallback('getLanguage', this.getLanguage); + + if (ExternalInterface.available) { + ExternalInterface.call("isConnected"); + } + } + + public function getDeviceFonts():Array + { + var embeddedAndDeviceFonts:Array = Font.enumerateFonts(true); + + var deviceFontNames:Array = []; + for each (var font:Font in embeddedAndDeviceFonts) + { + if (font.fontType == FontType.EMBEDDED + || font.fontStyle != FontStyle.REGULAR + ) + continue; + deviceFontNames.push(font.fontName); + } + + return deviceFontNames; + } + + public function getOS():String + { + return Capabilities.os; + } + + public function getResolution():Array + { + return [Capabilities.screenResolutionX,Capabilities.screenResolutionY]; + } + + public function getLanguage():String + { + return Capabilities.language; + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..3ab2ab1 --- /dev/null +++ b/README.md @@ -0,0 +1,17 @@ +# Am I Unique ? # + +This repository contains all the source code from the [AmIUnique.org](https://amiunique.org/) website. +This application was built using the [framework Play 2.3](https://playframework.com/) for the back-end and [Bootstrap](http://getbootstrap.com/) for the front-end. + +### Requirements ### +**JDK8** is needed to build the application. + +### Creation of the database ### +A database is needed to store the fingerprints. +To facilitate the creation process, the "**fpDB.sql**" file located at the root of the repository contains the structure of the table. You just need to import it with mysqldump or through phpMyAdmin to have everything set up properly. +Then, you have to modify the "**amiunique-source/website/conf/application.conf**" to include your database connection credentials (user and password). +Same action required in "**amiunique-source /website/conf/META-INF/persistence.xml**" to activate the persistence of data. + +### Launch of the application ### +Like other play applications, run "**./activator run**" at the root of the website directory to launch the web application. You will then be able to access your app via "**localhost:9000**". +You may also need to regenerate the Play application secret. To do so, run "**play-update-secret**" in the Play console. diff --git a/audio.sql b/audio.sql new file mode 100644 index 0000000..3fe79d6 --- /dev/null +++ b/audio.sql @@ -0,0 +1,77 @@ +-- phpMyAdmin SQL Dump +-- version 4.6.2 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost +-- Generation Time: Jun 22, 2016 at 11:37 AM +-- Server version: 10.0.23-MariaDB +-- PHP Version: 5.6.22 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `fingerprint` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `audio` +-- + +CREATE TABLE `audio` ( + `counter` int(11) NOT NULL, + `id` varchar(50) NOT NULL, + `acSampleRate` varchar(11) NOT NULL, + `acState` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `acMaxChannelCount` varchar(6) NOT NULL, + `acNumberOfInputs` varchar(6) NOT NULL, + `acNumberOfOutputs` varchar(6) NOT NULL, + `acChannelCount` varchar(6) NOT NULL, + `acChannelCountMode` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `acChannelInterpretation` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `anFftSize` varchar(6) NOT NULL, + `anFrequencyBinCount` varchar(6) NOT NULL, + `anMinDecibels` varchar(6) NOT NULL, + `anMaxDecibels` varchar(6) NOT NULL, + `anSmoothingTimeConstant` varchar(60) NOT NULL, + `anNumberOfInputs` varchar(6) NOT NULL, + `anNumberOfOutputs` varchar(6) NOT NULL, + `anChannelCount` varchar(6) NOT NULL, + `anChannelCountMode` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `anChannelInterpretation` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `audioDynSum` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `audioDynHash` varchar(40) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `audioPoints` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL, + `audioDynPoints` text CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=latin1; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `audio` +-- +ALTER TABLE `audio` + ADD PRIMARY KEY (`counter`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `audio` +-- +ALTER TABLE `audio` + MODIFY `counter` int(11) NOT NULL AUTO_INCREMENT; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/blockExtensions.sql b/blockExtensions.sql new file mode 100644 index 0000000..9b3cb6c --- /dev/null +++ b/blockExtensions.sql @@ -0,0 +1,68 @@ +-- phpMyAdmin SQL Dump +-- version 4.8.3 +-- https://www.phpmyadmin.net/ +-- +-- Host: localhost +-- Generation Time: Oct 18, 2018 at 04:31 PM +-- Server version: 8.0.12 +-- PHP Version: 7.1.16 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET AUTOCOMMIT = 0; +START TRANSACTION; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `fingerprint` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `blockExtensions` +-- + +CREATE TABLE `blockExtensions` ( + `counter` int(11) NOT NULL, + `id` varchar(50) NOT NULL, + `latest_links` longtext NOT NULL, + `lowe_links` longtext NOT NULL, + `old_links` longtext NOT NULL, + `random_links` longtext NOT NULL, + `latest_results` longtext NOT NULL, + `lowe_results` longtext NOT NULL, + `old_results` longtext NOT NULL, + `random_results` longtext NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci; + + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `blockExtensions` +-- +ALTER TABLE `blockExtensions` + ADD PRIMARY KEY (`counter`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `blockExtensions` +-- +ALTER TABLE `blockExtensions` + MODIFY `counter` int(11) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=8; +COMMIT; + +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/combinationStats.sql b/combinationStats.sql new file mode 100644 index 0000000..9329328 --- /dev/null +++ b/combinationStats.sql @@ -0,0 +1,57 @@ +-- phpMyAdmin SQL Dump +-- version 4.4.11 +-- http://www.phpmyadmin.net +-- +-- Host: localhost +-- Generation Time: Jul 27, 2015 at 08:06 AM +-- Server version: 10.0.20-MariaDB +-- PHP Version: 5.6.10 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Database: `fingerprint` +-- + +-- -------------------------------------------------------- + +-- +-- Table structure for table `combinationStats` +-- + +CREATE TABLE IF NOT EXISTS `combinationStats` ( + `counter` int(10) unsigned NOT NULL, + `combination` mediumtext COLLATE utf8_unicode_ci NOT NULL, + `indicator` varchar(250) COLLATE utf8_unicode_ci NOT NULL, + `number` int(11) NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Indexes for dumped tables +-- + +-- +-- Indexes for table `combinationStats` +-- +ALTER TABLE `combinationStats` + ADD PRIMARY KEY (`counter`); + +-- +-- AUTO_INCREMENT for dumped tables +-- + +-- +-- AUTO_INCREMENT for table `combinationStats` +-- +ALTER TABLE `combinationStats` + MODIFY `counter` int(10) unsigned NOT NULL AUTO_INCREMENT; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/extensionData.sql b/extensionData.sql new file mode 100644 index 0000000..81bd56e --- /dev/null +++ b/extensionData.sql @@ -0,0 +1,116 @@ +-- phpMyAdmin SQL Dump +-- version 4.5.4.1deb2ubuntu2 +-- http://www.phpmyadmin.net +-- +-- Client : localhost +-- Généré le : Mar 23 Mai 2017 à 17:33 +-- Version du serveur : 5.7.18-0ubuntu0.16.04.1 +-- Version de PHP : 7.0.15-0ubuntu0.16.04.4 + +SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; +SET time_zone = "+00:00"; + + +/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; +/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; +/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; +/*!40101 SET NAMES utf8mb4 */; + +-- +-- Base de données : `fingerprint` +-- + +-- -------------------------------------------------------- + +-- +-- Structure de la table `extensionData` +-- + +CREATE TABLE `extensionData` ( + `counter` int(11) NOT NULL, + `id` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, + `addressHttp` varchar(50) COLLATE utf8_unicode_ci NOT NULL, + `creationDate` datetime NOT NULL, + `updateDate` datetime DEFAULT NULL, + `endDate` datetime DEFAULT NULL, + `userAgentHttp` varchar(300) COLLATE utf8_unicode_ci DEFAULT NULL, + `acceptHttp` varchar(300) COLLATE utf8_unicode_ci DEFAULT NULL, + `hostHttp` varchar(100) COLLATE utf8_unicode_ci NOT NULL, + `connectionHttp` varchar(100) COLLATE utf8_unicode_ci NOT NULL, + `encodingHttp` varchar(200) COLLATE utf8_unicode_ci NOT NULL, + `languageHttp` varchar(200) COLLATE utf8_unicode_ci NOT NULL, + `orderHttp` varchar(200) COLLATE utf8_unicode_ci NOT NULL, + `pluginsJS` text COLLATE utf8_unicode_ci NOT NULL, + `platformJS` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, + `cookiesJS` varchar(10) COLLATE utf8_unicode_ci NOT NULL, + `dntJS` varchar(10) COLLATE utf8_unicode_ci NOT NULL, + `timezoneJS` varchar(10) COLLATE utf8_unicode_ci NOT NULL, + `resolutionJS` varchar(20) COLLATE utf8_unicode_ci NOT NULL, + `localJS` varchar(10) COLLATE utf8_unicode_ci NOT NULL, + `sessionJS` varchar(10) COLLATE utf8_unicode_ci NOT NULL, + `IEDataJS` varchar(10) COLLATE utf8_unicode_ci NOT NULL, + `canvasJS` mediumtext COLLATE utf8_unicode_ci, + `webGLJs` mediumtext COLLATE utf8_unicode_ci, + `fontsFlash` mediumtext COLLATE utf8_unicode_ci, + `resolutionFlash` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, + `languageFlash` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, + `platformFlash` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, + `adBlock` varchar(10) COLLATE utf8_unicode_ci NOT NULL, + `vendorWebGLJS` varchar(200) COLLATE utf8_unicode_ci NOT NULL, + `rendererWebGLJS` varchar(200) COLLATE utf8_unicode_ci NOT NULL, + `octaneScore` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL, + `sunspiderTime` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL, + `pluginsJSHashed` varchar(40) COLLATE utf8_unicode_ci NOT NULL, + `canvasJSHashed` varchar(40) COLLATE utf8_unicode_ci NOT NULL, + `webGLJsHashed` varchar(40) COLLATE utf8_unicode_ci NOT NULL, + `fontsFlashHashed` varchar(40) COLLATE utf8_unicode_ci NOT NULL, + `hardwareConcurrency` varchar(2) COLLATE utf8_unicode_ci DEFAULT NULL, + `availableScreenResolution` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL, + `cpuClass` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL, + `modernizr` text COLLATE utf8_unicode_ci, + `overwrittenObjects` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL, + `osMediaQueries` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL, + `appCodeName` varchar(25) COLLATE utf8_unicode_ci DEFAULT NULL, + `oscpu` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL, + `appName` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL, + `appVersion` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL, + `languages` varchar(100) COLLATE utf8_unicode_ci DEFAULT NULL, + `mimeTypes` text COLLATE utf8_unicode_ci, + `pluginsUsingMimeTypes` text COLLATE utf8_unicode_ci, + `product` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL, + `productSub` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL, + `vendor` varchar(20) COLLATE utf8_unicode_ci DEFAULT NULL, + `vendorSub` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL, + `touchSupport` varchar(25) COLLATE utf8_unicode_ci DEFAULT NULL, + `buildID` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL, + `navigatorPrototype` text COLLATE utf8_unicode_ci, + `mathsConstants` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL, + `resOverflow` varchar(150) COLLATE utf8_unicode_ci DEFAULT NULL, + `errorsGenerated` varchar(250) COLLATE utf8_unicode_ci DEFAULT NULL, + `unknownImageError` varchar(15) COLLATE utf8_unicode_ci DEFAULT NULL, + `fontsEnum` text COLLATE utf8_unicode_ci, + `audio` text COLLATE utf8_unicode_ci +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + +-- +-- Index pour les tables exportées +-- + +-- +-- Index pour la table `extensionData` +-- +ALTER TABLE `extensionData` + ADD PRIMARY KEY (`counter`); + +-- +-- AUTO_INCREMENT pour les tables exportées +-- + +-- +-- AUTO_INCREMENT pour la table `extensionData` +-- +ALTER TABLE `extensionData` + MODIFY `counter` int(11) NOT NULL AUTO_INCREMENT; +/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; +/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; +/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/fpDB.sql b/fpDB.sql index b5c1942..848b172 100644 --- a/fpDB.sql +++ b/fpDB.sql @@ -1,11 +1,11 @@ -- phpMyAdmin SQL Dump --- version 4.2.11 +-- version 4.4.11 -- http://www.phpmyadmin.net -- -- Host: localhost --- Generation Time: Dec 02, 2014 at 10:09 AM --- Server version: 5.5.39-MariaDB --- PHP Version: 5.5.18 +-- Generation Time: Jul 27, 2015 at 08:05 AM +-- Server version: 10.0.20-MariaDB +-- PHP Version: 5.6.10 SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO"; SET time_zone = "+00:00"; @@ -14,7 +14,7 @@ SET time_zone = "+00:00"; /*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */; /*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */; /*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */; -/*!40101 SET NAMES utf8 */; +/*!40101 SET NAMES utf8mb4 */; -- -- Database: `fingerprint` @@ -27,7 +27,7 @@ SET time_zone = "+00:00"; -- CREATE TABLE IF NOT EXISTS `fpData` ( -`counter` int(11) NOT NULL, + `counter` int(11) NOT NULL, `id` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, `addressHttp` varchar(50) COLLATE utf8_unicode_ci NOT NULL, `time` datetime NOT NULL, @@ -54,9 +54,15 @@ CREATE TABLE IF NOT EXISTS `fpData` ( `languageFlash` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, `platformFlash` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL, `adBlock` varchar(10) COLLATE utf8_unicode_ci NOT NULL, + `vendorWebGLJS` varchar(200) COLLATE utf8_unicode_ci NOT NULL, + `rendererWebGLJS` varchar(200) COLLATE utf8_unicode_ci NOT NULL, `octaneScore` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL, - `sunspiderTime` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL -) ENGINE=InnoDB AUTO_INCREMENT=2821 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; + `sunspiderTime` varchar(10) COLLATE utf8_unicode_ci DEFAULT NULL, + `pluginsJSHashed` varchar(40) COLLATE utf8_unicode_ci NOT NULL, + `canvasJSHashed` varchar(40) COLLATE utf8_unicode_ci NOT NULL, + `webGLJsHashed` varchar(40) COLLATE utf8_unicode_ci NOT NULL, + `fontsFlashHashed` varchar(40) COLLATE utf8_unicode_ci NOT NULL +) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci; -- -- Indexes for dumped tables @@ -66,7 +72,28 @@ CREATE TABLE IF NOT EXISTS `fpData` ( -- Indexes for table `fpData` -- ALTER TABLE `fpData` - ADD PRIMARY KEY (`counter`); + ADD PRIMARY KEY (`counter`), + ADD KEY `userAgentHttp` (`userAgentHttp`(255)), + ADD KEY `localJS` (`localJS`), + ADD KEY `dntJS` (`dntJS`), + ADD KEY `acceptHttp` (`acceptHttp`(255)), + ADD KEY `hostHttp` (`hostHttp`), + ADD KEY `connectionHttp` (`connectionHttp`), + ADD KEY `encodingHttp` (`encodingHttp`), + ADD KEY `languageHttp` (`languageHttp`), + ADD KEY `orderHttp` (`orderHttp`), + ADD KEY `platformJS` (`platformJS`), + ADD KEY `cookiesJS` (`cookiesJS`), + ADD KEY `timezoneJS` (`timezoneJS`), + ADD KEY `resolutionJS` (`resolutionJS`), + ADD KEY `sessionJS` (`sessionJS`), + ADD KEY `IEDataJS` (`IEDataJS`), + ADD KEY `resolutionFlash` (`resolutionFlash`), + ADD KEY `languageFlash` (`languageFlash`), + ADD KEY `platformFlash` (`platformFlash`), + ADD KEY `adBlock` (`adBlock`), + ADD KEY `vendorWebGLJS` (`vendorWebGLJS`), + ADD KEY `rendererWebGLJS` (`rendererWebGLJS`); -- -- AUTO_INCREMENT for dumped tables @@ -76,7 +103,7 @@ ALTER TABLE `fpData` -- AUTO_INCREMENT for table `fpData` -- ALTER TABLE `fpData` -MODIFY `counter` int(11) NOT NULL AUTO_INCREMENT,AUTO_INCREMENT=2821; + MODIFY `counter` int(11) NOT NULL AUTO_INCREMENT; /*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */; /*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */; /*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */; diff --git a/website/.gitignore b/website/.gitignore index 112855a..93e315e 100644 --- a/website/.gitignore +++ b/website/.gitignore @@ -14,3 +14,4 @@ dist /.project /RUNNING_PID /.settings +/conf diff --git a/website/activator b/website/activator index b3d7f8c..b19a051 100755 --- a/website/activator +++ b/website/activator @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash ### ------------------------------- ### ### Helper methods for BASH scripts ### @@ -8,14 +8,14 @@ realpath () { ( TARGET_FILE="$1" - cd $(dirname "$TARGET_FILE") + cd "$(dirname "$TARGET_FILE")" TARGET_FILE=$(basename "$TARGET_FILE") COUNT=0 while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ] do TARGET_FILE=$(readlink "$TARGET_FILE") - cd $(dirname "$TARGET_FILE") + cd "$(dirname "$TARGET_FILE")" TARGET_FILE=$(basename "$TARGET_FILE") COUNT=$(($COUNT + 1)) done @@ -157,14 +157,6 @@ require_arg () { die "$opt requires <$type> argument" fi } -require_arg () { - local type="$1" - local opt="$2" - local arg="$3" - if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then - die "$opt requires <$type> argument" - fi -} is_function_defined() { declare -f "$1" > /dev/null } @@ -296,11 +288,11 @@ declare -a java_args declare -a app_commands declare -r real_script_path="$(realpath "$0")" declare -r activator_home="$(realpath "$(dirname "$real_script_path")")" -declare -r app_version="1.2.10" +declare -r app_version="1.3.6" declare -r app_launcher="${activator_home}/activator-launch-${app_version}.jar" declare -r script_name=activator -declare -r java_cmd=$(get_java_cmd) +java_cmd=$(get_java_cmd) declare -r java_opts=( "${ACTIVATOR_OPTS[@]}" "${SBT_OPTS[@]}" "${JAVA_OPTS[@]}" "${java_opts[@]}" ) userhome="$HOME" if is_cygwin; then diff --git a/website/activator-launch-1.2.10.jar b/website/activator-launch-1.2.10.jar deleted file mode 100644 index 0e17413..0000000 Binary files a/website/activator-launch-1.2.10.jar and /dev/null differ diff --git a/website/activator-launch-1.3.6.jar b/website/activator-launch-1.3.6.jar new file mode 100644 index 0000000..f6f665f Binary files /dev/null and b/website/activator-launch-1.3.6.jar differ diff --git a/website/activator.bat b/website/activator.bat index 608d7dd..709b215 100755 --- a/website/activator.bat +++ b/website/activator.bat @@ -1,6 +1,7 @@ @REM activator launcher script @REM -@REM Envioronment: +@REM Environment: +@REM In order for Activator to work you must have Java available on the classpath @REM JAVA_HOME - location of a JDK home dir (optional if java on path) @REM CFG_OPTS - JVM options (optional) @REM Configuration: @@ -29,6 +30,8 @@ if defined var1 ( echo SBT_OPTS Environment variable, if unset uses "" echo ACTIVATOR_OPTS Environment variable, if unset uses "" echo. + echo Please note that in order for Activator to work you must have Java available on the classpath + echo. goto :end ) ) @@ -40,7 +43,7 @@ if "%ACTIVATOR_HOME%"=="" ( ) set ERROR_CODE=0 -set APP_VERSION=1.2.10 +set APP_VERSION=1.3.6 set ACTIVATOR_LAUNCH_JAR=activator-launch-%APP_VERSION%.jar rem Detect if we were double clicked, although theoretically A user could @@ -86,7 +89,8 @@ if "%_JAVACMD%"=="" set _JAVACMD=java rem Detect if this java is ok to use. for /F %%j in ('"%_JAVACMD%" -version 2^>^&1') do ( - if %%~j==Java set JAVAINSTALLED=1 + if %%~j==java set JAVAINSTALLED=1 + if %%~j==openjdk set JAVAINSTALLED=1 ) rem Detect the same thing about javac diff --git a/website/app/Global.java b/website/app/Global.java new file mode 100644 index 0000000..1ca18f8 --- /dev/null +++ b/website/app/Global.java @@ -0,0 +1,30 @@ +import play.Logger; +import play.libs.Akka; +import scala.concurrent.duration.Duration; +import akka.actor.ActorRef; +import akka.actor.Props; +import actors.*; + +import play.Application; +import play.GlobalSettings; +import java.util.concurrent.TimeUnit; + +public class Global extends GlobalSettings { + + @Override + public void onStart(Application app) { + ActorRef myActor = Akka.system().actorOf(Props.create(UpdateDbActor.class)); + Akka.system().scheduler().scheduleOnce( + Duration.create(0, TimeUnit.MILLISECONDS), //Initial delay 0 milliseconds + myActor, + "hello", + Akka.system().dispatcher(), + null + ); + } + + @Override + public void onStop(Application app) { + Logger.info("Application shutdown..."); + } +} \ No newline at end of file diff --git a/website/app/actors/UpdateDbActor.java b/website/app/actors/UpdateDbActor.java new file mode 100644 index 0000000..faffe8b --- /dev/null +++ b/website/app/actors/UpdateDbActor.java @@ -0,0 +1,68 @@ +package actors; + +import akka.actor.ActorPath; +import akka.actor.ActorRef; +import akka.actor.UntypedActor; +import models.*; +import java.util.*; +import java.util.regex.Pattern; +import java.util.regex.Matcher; + +public class UpdateDbActor extends UntypedActor { + + public UpdateDbActor() {} + + @Override + public void onReceive(Object msg) throws Exception { + FpDataEntityManager em = new FpDataEntityManager(); + CombinationStatsEntityManager emc = new CombinationStatsEntityManager(); + + if(emc.getNumberOfEntries()<5) { + + String[] fields = {"userAgentHttp", "acceptHttp", "connectionHttp", "languageHttp", "orderHttp", "encodingHttp", "pluginsJsHashed", "platformJs", "cookiesJs", "dntJs", "timezoneJs", "resolutionJs", "localJs", + "sessionJs", "ieDataJs", "canvasJsHashed", "fontsFlashHashed", "resolutionFlash", "languageFlash", "platformFlash", "adBlock"}; + int nbEntries = em.getNumberOfEntries(); + + ArrayList plugins = em.getAttribute("pluginsJs"); + HashSet pluginsAdded = new HashSet(); + + String patternStringPlugin = "Plugin [0-9]+: ([a-zA-Z -.]+)"; + Pattern pattern = Pattern.compile(patternStringPlugin); + + int j = 1; + for (String rowPlugins : plugins) { + Matcher matcher = pattern.matcher(rowPlugins); + + while (matcher.find()) { + String plugin = matcher.group(1); + if (!pluginsAdded.contains(plugin) && plugins != null) { + long nbSimilarPlugins = emc.getNbIdenticalPlugins(plugin); + if (emc.testExistingCombination(plugin)) { + emc.updateCombinationStats(plugin, "pluginsJs", nbSimilarPlugins); + } else { + emc.createCombinationStats(plugin, "pluginsJs", nbSimilarPlugins); + } + pluginsAdded.add(plugin); + j++; + } + } + } + + for (String attribute : fields) { + List res = em.getStatsAttribute(attribute); + + for (Object[] resultElement : res) { + String combination = (String) resultElement[0]; + if (combination == null) { + combination = ""; + } + Long nb = (Long) resultElement[1]; + if (emc.updateCombinationStats(combination, attribute, nb) == 0) { + emc.createCombinationStats(combination, attribute, nb); + } + } + } + } + } + +} \ No newline at end of file diff --git a/website/app/controllers/Application.java b/website/app/controllers/Application.java index 4968785..63df658 100644 --- a/website/app/controllers/Application.java +++ b/website/app/controllers/Application.java @@ -1,25 +1,9 @@ package controllers; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import models.*; -import org.apache.commons.codec.digest.DigestUtils; import play.Routes; -import play.cache.Cache; -import play.libs.Json; import play.mvc.*; - import views.html.*; -import java.sql.Timestamp; -import java.time.LocalDateTime; -import java.time.temporal.ChronoUnit; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -//@With(ForceHttps.class) public class Application extends Controller { public static Result home() { @@ -30,13 +14,6 @@ public static Result privacy() { return ok(privacy.render()); } - public static Result fp() { - if(request().cookies().get("amiunique") == null){ - response().setCookie("amiunique",UUID.randomUUID().toString(),60*60*24*120,"/","amiunique.org",true,true); - } - return ok(fp.render(request())); - } - public static Result faq(){ return ok(faq.render()); } @@ -49,178 +26,14 @@ public static Result about(){ return ok(about.render()); } - public static Result fpNoJs() { - Http.Cookie cookie = request().cookies().get("amiunique"); - String id; - if(cookie == null){ - id = "Not supported"; - } else { - id = cookie.value(); - } - - FpDataEntityManager em = new FpDataEntityManager(); - FpDataEntity fp; - - if(id != "Not supported" && em.checkIfFPWithNoJsExists(id, request().getHeader("User-Agent"), - request().getHeader("Accept"), request().getHeader("Connection"), - request().getHeader("Accept-Encoding"), request().getHeader("Accept-Language"))){ - fp = em.getExistingFP(id); - } else { - LocalDateTime time = LocalDateTime.now(); - time = time.truncatedTo(ChronoUnit.HOURS); - fp = em.createWithoutJavaScript(id, - DigestUtils.sha1Hex(request().remoteAddress()), Timestamp.valueOf(time), request().getHeader("User-Agent"), - request().getHeader("Accept"), request().getHeader("Host"), request().getHeader("Connection"), - request().getHeader("Accept-Encoding"), request().getHeader("Accept-Language"), - request().headers().keySet().toString().replaceAll("[,\\[\\]]", "")); - } - - ObjectNode node = (ObjectNode) Json.toJson(fp); - node.remove("counter"); - node.remove("octaneScore"); - node.remove("sunspiderTime"); - node.remove("addressHttp"); - node.remove("time"); - node.remove("hostHttp"); - node.remove("orderHttp"); - node.remove("id"); - JsonNode json = (JsonNode) node; - - //Get the surprisal and entropy of every attribute - Map percentages = em.getPercentages(json); - - //Number of fingerprints - Integer nbTotal = em.getNumberOfEntries(); - //Number of identical fingerprints - Integer nbIdent = em.getNumberOfIdenticalFingerprints(json); - - //Analyse the user agent - ParsedFP parsedFP = new ParsedFP(node.get("userAgentHttp").asText()); - //Analyse the language and timezone - parsedFP.setLanguage(node.get("languageHttp").asText()); - - //Get some general stats - HashMap> resMap = em.getOSBrowserStats(); - HashMap osMap = resMap.get("os"); - HashMap browsersMap = resMap.get("browsers"); - Vermap langMap = em.getLanguageStats(); - - //Adding percentages for OS and browsers - for (Map.Entry entry : osMap.entrySet()) { - percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); - } - for (Map.Entry entry : browsersMap.entrySet()) { - percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); - } - - //Render the FP + Stats - return ok(fpNoJs.render(json, parsedFP, Json.toJson(percentages),Json.toJson(osMap), - Json.toJson(browsersMap),Json.toJson(langMap),nbTotal,nbIdent)); - } - - - public static Result addFingerprint() { - Http.Cookie cookie = request().cookies().get("amiunique"); - String id; - if(cookie == null){ - id = "Not supported"; - } else { - id = cookie.value(); - } - - //Get FP attributes (body content) - JsonNode json = request().body().asJson(); - FpDataEntityManager em = new FpDataEntityManager(); - FpDataEntity fp; - - if(id != "Not supported" && em.checkIfFPExists(id,json.get("userAgentHttp").asText(), - json.get("acceptHttp").asText(), json.get("connectionHttp").asText(), - json.get("encodingHttp").asText(), json.get("languageHttp").asText(), - json.get("pluginsJs").asText(), json.get("platformJs").asText(), json.get("cookiesJs").asText(), - json.get("dntJs").asText(), json.get("timezoneJs").asText(), json.get("resolutionJs").asText(), - json.get("localJs").asText(), json.get("sessionJs").asText(), json.get("IEDataJs").asText(), - json.get("canvasJs").asText(), json.get("webGLJs").asText(), json.get("fontsFlash").asText(), - json.get("resolutionFlash").asText(), json.get("languageFlash").asText(), json.get("platformFlash").asText(), - json.get("adBlock").asText())){ - fp = em.getExistingFP(id); - } else { - LocalDateTime time = LocalDateTime.now(); - time = time.truncatedTo(ChronoUnit.HOURS); - - fp = em.createFull(id, - DigestUtils.sha1Hex(request().remoteAddress()), Timestamp.valueOf(time), json.get("userAgentHttp").asText(), - json.get("acceptHttp").asText(), json.get("hostHttp").asText(), json.get("connectionHttp").asText(), - json.get("encodingHttp").asText(), json.get("languageHttp").asText(), json.get("orderHttp").asText(), - json.get("pluginsJs").asText(), json.get("platformJs").asText(), json.get("cookiesJs").asText(), - json.get("dntJs").asText(), json.get("timezoneJs").asText(), json.get("resolutionJs").asText(), - json.get("localJs").asText(), json.get("sessionJs").asText(), json.get("IEDataJs").asText(), - json.get("canvasJs").asText(), json.get("webGLJs").asText(), json.get("fontsFlash").asText(), - json.get("resolutionFlash").asText(), json.get("languageFlash").asText(), json.get("platformFlash").asText(), - json.get("adBlock").asText(), "", ""); - } - - ObjectNode node = (ObjectNode) Json.toJson(fp); - node.remove("counter"); - node.remove("octaneScore"); - node.remove("sunspiderTime"); - node.remove("addressHttp"); - node.remove("time"); - node.remove("hostHttp"); - node.remove("orderHttp"); - node.remove("id"); - json = (JsonNode) node; - - //Analyse the user agent - ParsedFP parsedFP = new ParsedFP(node.get("userAgentHttp").asText()); - //Analyse the language and timezone - parsedFP.setLanguage(node.get("languageHttp").asText()); - parsedFP.setTimezone(node.get("timezoneJs").asText()); - - //Number of fingerprints - Integer nbTotal = em.getNumberOfEntries(); - //Number of identical fingerprints - Integer nbIdent = em.getNumberOfIdenticalFingerprints(json); - - //Get the percentages of every attribute - Map percentages = em.getPercentages(json); - - //Get some general stats - HashMap> resMap = em.getOSBrowserStats(); - HashMap osMap = resMap.get("os"); - HashMap browsersMap = resMap.get("browsers"); - Vermap langMap = em.getLanguageStats(); - ArrayList timezoneMap = em.getTimezoneStats(); - - //Adding percentages for OS and browsers - for (Map.Entry entry : osMap.entrySet()) { - percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); - } - for (Map.Entry entry : browsersMap.entrySet()) { - percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); - } - - //Render the FP + Stats - return ok(results.render(json, parsedFP, Json.toJson(percentages),Json.toJson(osMap), - Json.toJson(browsersMap),Json.toJson(langMap),Json.toJson(timezoneMap),nbTotal,nbIdent)); + public static Result tools() { + return ok(tools.render()); } public static Result jsRoutes(){ response().setContentType("text/javascript"); - return ok(Routes.javascriptRouter("jsRoutes", routes.javascript.Application.addFingerprint())); - } - - public static Result stats() throws Exception{ - return ok(Cache.getOrElse("stats-html", () -> { - FpDataEntityManager em = new FpDataEntityManager(); - Integer nbTotal = em.getNumberOfEntries(); - Integer nbUnique = em.getNumberOfUniqueEntries(); - ArrayList timezone = em.getTimezoneStats(); - HashMap> resMap = em.getOSBrowserStats(); - Vermap langMap = em.getLanguageStats(); - Rangemap nbFontsList = em.getFontsStats(); - return stats.render(nbTotal,nbUnique,Json.toJson(timezone),Json.toJson(resMap.get("browsers")), - Json.toJson(resMap.get("os")),Json.toJson(langMap),Json.toJson(nbFontsList)); - }, 1800)); + return ok(Routes.javascriptRouter("jsRoutes", routes.javascript.FPController.addFingerprint(), + routes.javascript.StatsController.percentages())); } } diff --git a/website/app/controllers/ExtensionController.java b/website/app/controllers/ExtensionController.java new file mode 100644 index 0000000..26c0328 --- /dev/null +++ b/website/app/controllers/ExtensionController.java @@ -0,0 +1,263 @@ +package controllers; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import models.ExtensionDataEntity; +import models.ExtensionDataEntityManager; +import org.apache.commons.codec.digest.DigestUtils; +import play.Play; +import play.libs.Json; +import play.mvc.Controller; +import play.mvc.Result; +import views.html.extension; +import views.html.noTimeline; +import views.html.timeline; +import views.html.timelineEmpty; + +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.*; + +public class ExtensionController extends Controller { + + /* + AmIUnique Extension + */ + + public static Result extension(){ + return ok(extension.render()); + } + + + public static Result addFingerprintFromExtension(String uuid){ + Map vals = request().body().asFormUrlEncoded(); + + if(uuid.length()>5) { + ExtensionDataEntityManager em = new ExtensionDataEntityManager(); + + String pluginsJsHashed = DigestUtils.sha1Hex(vals.get("pluginsJs")[0]); + String canvasJsHashed = DigestUtils.sha1Hex(vals.get("canvasJs")[0]); + String webGLJsHashed = DigestUtils.sha1Hex(vals.get("webGLJs")[0]); + String fontsFlashHashed = DigestUtils.sha1Hex(vals.get("fontsFlash")[0]); + + Timestamp currentTime = Timestamp.valueOf(LocalDateTime.now().truncatedTo(ChronoUnit.HOURS)); + + boolean create = false; + boolean update = false; + boolean end = false; + + //Check if FP with this ID exist + //If no, we add the presented FP + //If yes, we check if it is identical with the last one + // If no, we update the last one (endDate) and add the new one + // If yes, we update the last one (updateDate) + + if (em.checkIfIDExists(uuid)) { + if (em.checkIfLastFPIsIdentical(uuid, vals.get("userAgentHttp")[0], + vals.get("acceptHttp")[0], vals.get("encodingHttp")[0], vals.get("languageHttp")[0], + pluginsJsHashed, vals.get("platformJs")[0], vals.get("cookiesJs")[0], + vals.get("dntJs")[0], vals.get("timezoneJs")[0], vals.get("resolutionJs")[0], + vals.get("localJs")[0], vals.get("sessionJs")[0], vals.get("IEDataJs")[0], + canvasJsHashed, fontsFlashHashed, vals.get("resolutionFlash")[0], + vals.get("languageFlash")[0], vals.get("platformFlash")[0], vals.get("adBlock")[0])) { + update = true; + } else { + end = true; + create = true; + } + } else { + create = true; + } + + if (update) { + em.updateLastFP(uuid, currentTime); + } + + if (end) { + em.endLastFP(uuid, currentTime); + } + + if (create) { + String ip; + if(Play.isProd()) { + ip = FPController.getHeader(request(), "X-Real-IP"); + } else { + ip = request().remoteAddress(); + } + + em.createFP(uuid, + DigestUtils.sha1Hex(ip), currentTime, null, null, vals.get("userAgentHttp")[0], + vals.get("acceptHttp")[0], vals.get("hostHttp")[0], vals.get("connectionHttp")[0], + vals.get("encodingHttp")[0], vals.get("languageHttp")[0], vals.get("orderHttp")[0], + vals.get("pluginsJs")[0], vals.get("platformJs")[0], vals.get("cookiesJs")[0], + vals.get("dntJs")[0], vals.get("timezoneJs")[0], vals.get("resolutionJs")[0], + vals.get("localJs")[0], vals.get("sessionJs")[0], vals.get("IEDataJs")[0], + vals.get("canvasJs")[0], vals.get("webGLJs")[0], vals.get("fontsFlash")[0], + vals.get("resolutionFlash")[0], vals.get("languageFlash")[0], vals.get("platformFlash")[0], + vals.get("adBlock")[0], vals.get("vendorWebGLJs")[0], vals.get("rendererWebGLJs")[0], "", "", + pluginsJsHashed, canvasJsHashed, webGLJsHashed, fontsFlashHashed, + // new attributes start here + vals.get("hardwareConcurrency")[0], vals.get("availableScreenResolution")[0], + vals.get("cpuClass")[0], vals.get("modernizr")[0], vals.get("overwrittenObjects")[0], + vals.get("osMediaqueries")[0], vals.get("appCodeName")[0], vals.get("oscpu")[0], + vals.get("appName")[0], vals.get("appVersion")[0], vals.get("languages")[0], + vals.get("mimeTypes")[0], vals.get("pluginsUsingMimeTypes")[0], + vals.get("product")[0], vals.get("productSub")[0], vals.get("vendor")[0], + vals.get("vendorSub")[0], vals.get("touchSupport")[0], vals.get("buildID")[0], + vals.get("navigatorPrototype")[0], vals.get("mathsConstants")[0], + vals.get("resOverflow")[0], + vals.get("errorsGenerated")[0], vals.get("unknownImageError")[0], + vals.get("fontsEnum")[0], vals.get("audio")[0]); + } + + return ok(); + } else { + return ok(); + } + } + + public static Result getNbEvol(String uuid){ + if(uuid.length()>5) { + ExtensionDataEntityManager em = new ExtensionDataEntityManager(); + return ok(Integer.toString(em.getExistingFPsById(uuid).size() - 1)); + } else { + return ok(Integer.toString(0)); + } + } + + /* + Timeline + */ + + public static Result noTimeline(){ + return ok(noTimeline.render()); + } + + public static Result timeline(String id){ + + //We retrieve the FPs + ExtensionDataEntityManager edm = new ExtensionDataEntityManager(); + TreeSet fps = edm.getExistingFPsById(id); + if(fps.size()>0) { + + ExtensionDataEntity fp = edm.getLastFP(id); + ObjectNode node = (ObjectNode) Json.toJson(fp); + node.remove("counter"); + node.remove("octaneScore"); + node.remove("sunspiderTime"); + node.remove("addressHttp"); + node.remove("time"); + node.remove("hostHttp"); + node.remove("connectionHttp"); + node.remove("orderHttp"); + node.remove("id"); + node.remove("webGlJs"); + JsonNode jsonFP = (JsonNode) node; + + if (fps.size() > 1) { + fps = (TreeSet) fps.descendingSet(); + + HashMap differencesMap = new HashMap(); + Iterator it = fps.iterator(); + + //Initialisation for the first fingerprint + ExtensionDataEntity fp0 = it.next(); + HashMap att0 = fp0.fpToHashMap(); + HashMap tabHtmlDifferences = new HashMap(); + HashMap startDate = new HashMap(); + HashMap endDate = new HashMap(); + int numberFp = fps.size(); + int counterCanvas = 1; + + String firstDate = att0.get("creationDate"); + String currentDate = new SimpleDateFormat("yyyy,MM,dd,HH").format(new Date()); + + while (it.hasNext()) { + ExtensionDataEntity fp1 = it.next(); + HashMap att1 = fp1.fpToHashMap(); + + String diff = ""; + //We compare fp1 with fp0 + if (fp1.getUserAgentHttp() != null ? !fp1.getUserAgentHttp().equals(fp0.getUserAgentHttp()) : fp0.getUserAgentHttp() != null) + diff += "userAgentHttp, "; + if (fp1.getAcceptHttp() != null ? !fp1.getAcceptHttp().equals(fp0.getAcceptHttp()) : fp0.getAcceptHttp() != null) + diff += "acceptHttp, "; + if (fp1.getEncodingHttp() != null ? !fp1.getEncodingHttp().equals(fp0.getEncodingHttp()) : fp0.getEncodingHttp() != null) + diff += "encodingHttp, "; + if (fp1.getLanguageHttp() != null ? !fp1.getLanguageHttp().equals(fp0.getLanguageHttp()) : fp0.getLanguageHttp() != null) + diff += "languageHttp, "; + if (fp1.getPluginsJs() != null ? !fp1.getPluginsJs().equals(fp0.getPluginsJs()) : fp0.getPluginsJs() != null) + diff += "pluginsJs, "; + if (fp1.getPlatformJs() != null ? !fp1.getPlatformJs().equals(fp0.getPlatformJs()) : fp0.getPlatformJs() != null) + diff += "platformJs, "; + if (fp1.getCookiesJs() != null ? !fp1.getCookiesJs().equals(fp0.getCookiesJs()) : fp0.getCookiesJs() != null) + diff += "cookiesJs, "; + if (fp1.getDntJs() != null ? !fp1.getDntJs().equals(fp0.getDntJs()) : fp0.getDntJs() != null) + diff += "dntJs, "; + if (fp1.getTimezoneJs() != null ? !fp1.getTimezoneJs().equals(fp0.getTimezoneJs()) : fp0.getTimezoneJs() != null) + diff += "timezoneJs, "; + if (fp1.getResolutionJs() != null ? !fp1.getResolutionJs().equals(fp0.getResolutionJs()) : fp0.getResolutionJs() != null) + diff += "resolutionJs, "; + if (fp1.getLocalJs() != null ? !fp1.getLocalJs().equals(fp0.getLocalJs()) : fp0.getLocalJs() != null) + diff += "localJs, "; + if (fp1.getSessionJs() != null ? !fp1.getSessionJs().equals(fp0.getSessionJs()) : fp0.getSessionJs() != null) + diff += "sessionJs, "; + if (fp1.getCanvasJs() != null ? !fp1.getCanvasJs().equals(fp0.getCanvasJs()) : fp0.getCanvasJs() != null) + diff += "canvasJs, "; + if (fp1.getVendorWebGljs() != null ? !fp1.getVendorWebGljs().equals(fp0.getVendorWebGljs()) : fp0.getRendererWebGljs() != null) + diff += "vendorWebGLJs, "; + if (fp1.getRendererWebGljs() != null ? !fp1.getRendererWebGljs().equals(fp0.getRendererWebGljs()) : fp0.getRendererWebGljs() != null) + diff += "rendererWebGLJs, "; + if (fp1.getFontsFlash() != null ? !fp1.getFontsFlash().equals(fp0.getFontsFlash()) : fp0.getFontsFlash() != null) + diff += "fontsFlash, "; + if (fp1.getResolutionFlash() != null ? !fp1.getResolutionFlash().equals(fp0.getResolutionFlash()) : fp0.getResolutionFlash() != null) + diff += "resolutionFlash, "; + if (fp1.getLanguageFlash() != null ? !fp1.getLanguageFlash().equals(fp0.getLanguageFlash()) : fp0.getLanguageFlash() != null) + diff += "languageFlash, "; + if (fp1.getPlatformFlash() != null ? !fp1.getPlatformFlash().equals(fp0.getPlatformFlash()) : fp0.getPlatformFlash() != null) + diff += "platformFlash, "; + if (fp1.getAdBlock() != null ? !fp1.getAdBlock().equals(fp0.getAdBlock()) : fp0.getAdBlock() != null) + diff += "adBlock, "; + + try { + diff = diff.substring(0, diff.length() - 2); + String[] attDiff = diff.split(","); + String rowValue = ""; + for (String att : attDiff) { + att = att.trim(); + if (!att.equals("canvasJs")) { + rowValue += "" + att + "" + att0.get(att) + "" + att1.get(att) + ""; + } else { + rowValue += "" + att + ""; + counterCanvas += 2; + } + } + tabHtmlDifferences.put(fp1.getCounter(), rowValue); + startDate.put(fp1.getCounter(), att1.get("creationDate")); + endDate.put(fp0.getCounter(), att0.get("endDate")); + + differencesMap.put(fp1.getCounter(), diff); + fp0 = (ExtensionDataEntity) fp1.clone(); + att0 = (HashMap) att1.clone(); + } catch (StringIndexOutOfBoundsException e) { + String rowValue = ""; + tabHtmlDifferences.put(fp1.getCounter(), rowValue); + differencesMap.put(fp1.getCounter(), "nodiff"); + } + + } + + fps.remove(fps.last()); + return ok(timeline.render(jsonFP, Json.toJson(tabHtmlDifferences), Json.toJson(startDate), Json.toJson(endDate), firstDate, currentDate)); + } else { + return ok(timelineEmpty.render(jsonFP)); + } + } else { + return ok(noTimeline.render()); + } + } +} diff --git a/website/app/controllers/FPController.java b/website/app/controllers/FPController.java new file mode 100644 index 0000000..ad36082 --- /dev/null +++ b/website/app/controllers/FPController.java @@ -0,0 +1,380 @@ +package controllers; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import models.*; +import org.apache.commons.codec.digest.DigestUtils; +import play.Play; +import play.libs.Crypto; +import play.libs.Json; +import play.mvc.Controller; +import play.mvc.Http; +import play.mvc.Result; +import views.html.fp; +import views.html.fpNoJs; +import views.html.results; +import views.html.viewFP; + +import java.sql.Timestamp; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; +import java.util.UUID; + + +public class FPController extends Controller{ + + public static String getHeader(Http.Request request, String header){ + if(request.getHeader(header) == null){ + return "Not specified"; + } else { + return request.getHeader(header); + } + } + + public static String getAttribute(JsonNode json, String attribute){ + if(json.get(attribute) == null){ + return "Not specified"; + } else { + return json.get(attribute).asText(); + } + } + + public static Result fp() { + if(request().cookies().get("amiunique") == null){ + response().setCookie("amiunique", UUID.randomUUID().toString(),60*60*24*120,"/","amiunique.org",true,true); + response().setCookie("tempReturningVis","temp",60*60*12); + } + + return ok(fp.render(request())); + } + + public static Result fpNoJs() { + Http.Cookie cookie = request().cookies().get("amiunique"); + String id; + if(cookie == null){ + id = "Not supported"; + } else { + id = cookie.value(); + } + + FpDataEntityManager em = new FpDataEntityManager(); + CombinationStatsEntityManager emc = new CombinationStatsEntityManager(); + FpDataEntity fp; + boolean newFp; + + String noJS = "no JS"; + + if(!id.equals("Not supported") && em.checkIfFPWithNoJsExists(id, getHeader(request(), "User-Agent"), + getHeader(request(), "Accept"),getHeader(request(), "Accept-Encoding"), + getHeader(request(), "Accept-Language"))){ + fp = em.getExistingFPById(id); + newFp = false; + } else { + LocalDateTime time = LocalDateTime.now(); + time = time.truncatedTo(ChronoUnit.HOURS); + + String ip; + if(Play.isProd()) { + ip = getHeader(request(), "X-Real-IP"); + } else { + ip = request().remoteAddress(); + } + fp = em.createWithoutJavaScript(id, + DigestUtils.sha1Hex(ip), Timestamp.valueOf(time), getHeader(request(),"User-Agent"), + getHeader(request(),"Accept"), getHeader(request(),"Host"), getHeader(request(),"Connection"), + getHeader(request(),"Accept-Encoding"), getHeader(request(),"Accept-Language"), + request().headers().keySet().toString().replaceAll("[,\\[\\]]", "")); + + emc.updateCombinationStats(getHeader(request(),"User-Agent"), + getHeader(request(),"Accept"), getHeader(request(),"Connection"), + getHeader(request(),"Accept-Encoding"), getHeader(request(),"Accept-Language"), request().headers().keySet().toString().replaceAll("[,\\[\\]]", ""), + noJS, noJS, noJS, + noJS, noJS, noJS, + noJS, noJS, noJS, + noJS, noJS, noJS, + noJS, noJS, noJS, noJS); + + newFp = true; + } + + ObjectNode node = (ObjectNode) Json.toJson(fp); + + //Analyse the user agent + ParsedFP parsedFP = new ParsedFP(node.get("userAgentHttp").asText()); + //Analyse the language and timezone + parsedFP.setLanguage(node.get("languageHttp").asText()); + parsedFP.setTimezone(node.get("timezoneJs").asText()); + parsedFP.setNbFonts(node.get("fontsFlash").asText()); + + node.remove("counter"); + node.remove("octaneScore"); + node.remove("sunspiderTime"); + node.remove("addressHttp"); + node.remove("time"); + node.remove("hostHttp"); + node.remove("connectionHttp"); + node.remove("orderHttp"); + node.remove("ieDataJs"); + node.remove("id"); + node.remove("vendorWebGljs"); + node.remove("rendererWebGljs"); + node.remove("webGlJs"); + node.remove("canvasJs"); + node.remove("fontsFlash"); + node.remove("webGLJsHashed"); + JsonNode json = (JsonNode) node; + + //Get the stats instance + Stats s = Stats.getInstance(); + if(newFp) { + //Add the newly parsed FP to the stats + s.addFingerprint(parsedFP); + } + + //Number of fingerprints + Double nbTotal = s.getNbTotal().doubleValue(); + //Number of identical fingerprints + Integer nbIdent = em.getNumberOfIdenticalFingerprints(json); + //Get the percentages of every attribute + Map percentages = emc.getPercentages(json); + + percentages.put("pluginsJs", percentages.get("pluginsJsHashed")); + percentages.put("canvasJs", percentages.get("canvasJsHashed")); + percentages.put("fontsFlash", percentages.get("fontsFlashHashed")); + percentages.remove("pluginsJsHashed"); + percentages.remove("canvasJsHashed"); + percentages.remove("fontsFlashHashed"); + + node.put("canvasJs","no JS"); + node.put("vendorWebGljs","no JS"); + node.put("rendererWebGljs","no JS"); + node.put("fontsFlash","no JS"); + + //Get some general stats + HashMap osMap = s.getOs(); + HashMap browsersMap = s.getBrowsers(); + VersionMap langMap = s.getLanguages(); + + //Adding percentages for OS and browsers + for (Map.Entry entry : osMap.entrySet()) { + percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); + } + for (Map.Entry entry : browsersMap.entrySet()) { + percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); + } + + //Render the FP + models.Stats + return ok(fpNoJs.render(json, parsedFP, Json.toJson(percentages), Json.toJson(osMap), + Json.toJson(browsersMap), Json.toJson(langMap), nbTotal, nbIdent)); + } + + public static Result addFingerprint() { + Http.Cookie cookie = request().cookies().get("amiunique"); + String id; + if(cookie == null){ + id = "Not supported"; + } else { + id = cookie.value(); + } + + //Get FP attributes (body content) + JsonNode json = request().body().asJson(); + FpDataEntityManager em = new FpDataEntityManager(); + CombinationStatsEntityManager emc = new CombinationStatsEntityManager(); + FpDataEntity fp; + boolean newFp; + + String pluginsJsHashed = DigestUtils.sha1Hex(getAttribute(json,"pluginsJs")); + String canvasJsHashed = DigestUtils.sha1Hex(getAttribute(json,"canvasJs")); + String webGLJsHashed = DigestUtils.sha1Hex(getAttribute(json,"webGLJs")); + String fontsFlashHashed = DigestUtils.sha1Hex(getAttribute(json,"fontsFlash")); + + if(!id.equals("Not supported") && em.checkIfFPExists(id,getAttribute(json,"userAgentHttp"), + getAttribute(json,"acceptHttp"),getAttribute(json,"encodingHttp"), getAttribute(json,"languageHttp"), + pluginsJsHashed, getAttribute(json,"platformJs"), getAttribute(json,"cookiesJs"), + getAttribute(json,"dntJs"), getAttribute(json,"timezoneJs"), getAttribute(json,"resolutionJs"), + getAttribute(json,"localJs"), getAttribute(json,"sessionJs"), getAttribute(json,"IEDataJs"), + canvasJsHashed, webGLJsHashed, fontsFlashHashed, + getAttribute(json,"resolutionFlash"), getAttribute(json,"languageFlash"), getAttribute(json,"platformFlash"), + getAttribute(json,"adBlock"))){ + + fp = em.getExistingFPById(id); + newFp = false; + } else { + LocalDateTime time = LocalDateTime.now(); + time = time.truncatedTo(ChronoUnit.HOURS); + + String ip; + if(Play.isProd()) { + ip = getHeader(request(), "X-Real-IP"); + } else { + ip = request().remoteAddress(); + } + + fp = em.createFull(id, + DigestUtils.sha1Hex(ip), Timestamp.valueOf(time), getAttribute(json,"userAgentHttp"), + getAttribute(json,"acceptHttp"), getAttribute(json,"hostHttp"), getAttribute(json,"connectionHttp"), + getAttribute(json,"encodingHttp"), getAttribute(json,"languageHttp"), getAttribute(json,"orderHttp"), + getAttribute(json,"pluginsJs"), getAttribute(json,"platformJs"), getAttribute(json,"cookiesJs"), + getAttribute(json,"dntJs"), getAttribute(json,"timezoneJs"), getAttribute(json,"resolutionJs"), + getAttribute(json,"localJs"), getAttribute(json,"sessionJs"), getAttribute(json,"IEDataJs"), + getAttribute(json,"canvasJs"), getAttribute(json,"webGLJs"), getAttribute(json,"fontsFlash"), + getAttribute(json,"resolutionFlash"), getAttribute(json,"languageFlash"), getAttribute(json,"platformFlash"), + getAttribute(json,"adBlock"), getAttribute(json,"vendorWebGLJs"),getAttribute(json,"rendererWebGLJs"), "", "", + pluginsJsHashed, canvasJsHashed, webGLJsHashed, fontsFlashHashed); + + emc.updateCombinationStats(getAttribute(json,"userAgentHttp"), + getAttribute(json,"acceptHttp"), getAttribute(json,"connectionHttp"), + getAttribute(json,"encodingHttp"), getAttribute(json,"languageHttp"), getAttribute(json,"orderHttp"), + getAttribute(json,"pluginsJs"), getAttribute(json,"platformJs"), getAttribute(json,"cookiesJs"), + getAttribute(json,"dntJs"), getAttribute(json,"timezoneJs"), getAttribute(json,"resolutionJs"), + getAttribute(json,"localJs"), getAttribute(json,"sessionJs"), getAttribute(json,"IEDataJs"), + getAttribute(json,"resolutionFlash"), getAttribute(json,"languageFlash"), getAttribute(json,"platformFlash"), + getAttribute(json,"adBlock"), pluginsJsHashed, canvasJsHashed, fontsFlashHashed); + + //Add audio data + JsonNode audio = json.get("audioData"); + AudioEntityManager adem = new AudioEntityManager(); + String n = "Not"; + if(Objects.equals(audio.asText(), "Not supported")){ + adem.create(id, n, n, n, n, n,n, n, n, n, n,n, n, n, n, n,n, n, n, n, n, n,n); + } else { + JsonNode audioProp = audio.get("nt_vc_output"); + String acSampleRate=n, acState=n, acMaxChannelCount=n, acNumberOfInputs=n, acNumberOfOutputs=n, + acChannelCount=n, acChannelCountMode=n, acChannelInterpretation=n, anFftSize=n, anFrequencyBinCount=n, + anMinDecibels=n, anMaxDecibels=n, anSmoothingTimeConstant=n, anNumberOfInputs=n, anNumberOfOutputs=n, + anChannelCount=n, anChannelCountMode=n, anChannelInterpretation=n, audioDynSum=n, audioDynHash=n, audioPoints=n, + audioDynPoints=n; + if(!Objects.equals(audioProp.asText(), "Not available")) { + acSampleRate = audioProp.get("ac-sampleRate").asText(); + acState = audioProp.get("ac-state").asText(); + acMaxChannelCount = audioProp.get("ac-maxChannelCount").asText(); + acNumberOfInputs = audioProp.get("ac-numberOfInputs").asText(); + acNumberOfOutputs = audioProp.get("ac-numberOfOutputs").asText(); + acChannelCount = audioProp.get("ac-channelCount").asText(); + acChannelCountMode = audioProp.get("ac-channelCountMode").asText(); + acChannelInterpretation = audioProp.get("ac-channelInterpretation").asText(); + anFftSize = audioProp.get("an-fftSize").asText(); + anFrequencyBinCount = audioProp.get("an-frequencyBinCount").asText(); + anMinDecibels = audioProp.get("an-minDecibels").asText(); + anMaxDecibels = audioProp.get("an-maxDecibels").asText(); + anSmoothingTimeConstant = audioProp.get("an-smoothingTimeConstant").asText(); + anNumberOfInputs = audioProp.get("an-numberOfInputs").asText(); + anNumberOfOutputs = audioProp.get("an-numberOfOutputs").asText(); + anChannelCount = audioProp.get("an-channelCount").asText(); + anChannelCountMode = audioProp.get("an-channelCountMode").asText(); + anChannelInterpretation = audioProp.get("an-channelInterpretation").asText(); + } + if(audio.has("pxi_output")) { + audioDynSum = audio.get("pxi_output").asText(); + } + if(audio.has("pxi_full_buffer_hash")) { + audioDynHash = audio.get("pxi_full_buffer_hash").asText(); + } + if(audio.has("cc_output")) { + audioPoints = Json.stringify(audio.get("cc_output")); + } + if(audio.has("hybrid_output")) { + audioDynPoints = Json.stringify(audio.get("hybrid_output")); + } + + adem.create(id, acSampleRate, acState, acMaxChannelCount, acNumberOfInputs, acNumberOfOutputs, + acChannelCount, acChannelCountMode, acChannelInterpretation, anFftSize, anFrequencyBinCount, + anMinDecibels, anMaxDecibels, anSmoothingTimeConstant, anNumberOfInputs, anNumberOfOutputs, + anChannelCount, anChannelCountMode, anChannelInterpretation, audioDynSum, audioDynHash, audioPoints, + audioDynPoints); + } + + //Add blockExtensions data + JsonNode blockExtensionsData = json.get("blockExtensionsData"); + BlockExtensionsEntityManager beem = new BlockExtensionsEntityManager(); + + if(Objects.equals(blockExtensionsData.asText(), "Not supported")) { + beem.create(id, n, n, n, n, n, n, n, n); + } else { + + JsonNode latest_links = blockExtensionsData.get("latest_links"); + JsonNode lowe_links = blockExtensionsData.get("lowe_links"); + JsonNode old_links = blockExtensionsData.get("old_links"); + JsonNode random_links = blockExtensionsData.get("random_links"); + JsonNode latest_results = blockExtensionsData.get("latest_results"); + JsonNode lowe_results = blockExtensionsData.get("lowe_results"); + JsonNode old_results = blockExtensionsData.get("old_results"); + JsonNode random_results = blockExtensionsData.get("random_results"); + + beem.create(id, latest_links.toString(), lowe_links.toString(), old_links.toString(), random_links.toString(), latest_results.toString(), + lowe_results.toString(), old_results.toString(), random_results.toString()); + } + + newFp = true; + } + + ObjectNode node = (ObjectNode) Json.toJson(fp); + int counter = node.get("counter").asInt(); + node.remove("counter"); + node.remove("octaneScore"); + node.remove("sunspiderTime"); + node.remove("addressHttp"); + node.remove("time"); + node.remove("hostHttp"); + node.remove("connectionHttp"); + node.remove("orderHttp"); + node.remove("id"); + node.remove("webGlJs"); + json = (JsonNode) node; + + //Analyse the user agent + ParsedFP parsedFP = new ParsedFP(node.get("userAgentHttp").asText()); + //Analyse the language and timezone + parsedFP.setLanguage(node.get("languageHttp").asText()); + parsedFP.setTimezone(node.get("timezoneJs").asText()); + parsedFP.setNbFonts(node.get("fontsFlash").asText()); + + //Get the stats instance + Stats s = Stats.getInstance(); + if(newFp) { + //Add the newly parsed FP to the stats + s.addFingerprint(parsedFP); + } + + //Number of fingerprints + Double nbTotal = s.getNbTotal().doubleValue(); + //Number of identical fingerprints + Integer nbIdent = em.getNumberOfIdenticalFingerprints(json); + //Get the percentages of every attribute + //Map percentages = em.getPercentages(json); + Map percentages = new HashMap<>(); + + //Get some general stats + HashMap osMap = s.getOs(); + HashMap browsersMap = s.getBrowsers(); + VersionMap langMap = s.getLanguages(); + CounterMap timezoneMap = s.getTimezone(); + + //Adding percentages for OS and browsers + for (Map.Entry entry : osMap.entrySet()) { + percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); + } + for (Map.Entry entry : browsersMap.entrySet()) { + percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); + } + + ObjectNode n = Json.newObject(); + n.put("c",counter); + n.put("t", Crypto.generateToken()); + String c = Crypto.encryptAES(n.toString()); + + //Render the FP + Stats + return ok(results.render(json, parsedFP, Json.toJson(percentages), Json.toJson(osMap), + Json.toJson(browsersMap), Json.toJson(langMap), Json.toJson(timezoneMap), nbTotal, + nbIdent, c)); + } + + public static Result viewFP() { + return ok(viewFP.render(request())); + } + +} diff --git a/website/app/controllers/HistoryController.java b/website/app/controllers/HistoryController.java new file mode 100644 index 0000000..c339a8b --- /dev/null +++ b/website/app/controllers/HistoryController.java @@ -0,0 +1,127 @@ +package controllers; + +import models.FpDataEntity; +import models.FpDataEntityManager; +import org.apache.commons.lang3.time.DateFormatUtils; +import play.mvc.Controller; +import play.mvc.Http; +import play.mvc.Result; +import views.html.differences; +import views.html.history; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.TreeSet; + +public class HistoryController extends Controller { + + public static Result history(){ + + Http.Cookie cookie = request().cookies().get("amiunique"); + String id; + if(cookie == null){ + return redirect("/"); + } + + id = cookie.value(); + FpDataEntityManager emf = new FpDataEntityManager(); + + TreeSet fps = emf.getExistingFPsById(id); + + return ok(history.render(fps)); + } + + /* + Method called with ajax on my history page + It receives counters of fingerprints to compare + It compares fingerprint(t+1) with fingerprint(t) + It builds an HTML tab which is inserted in the "my history" page + */ + public static Result compareFpHistory(){ + String[] values = request().body().asFormUrlEncoded().get("list")[0].split(","); + ArrayList valuesCasted = new ArrayList(); + for(String s : values){ + valuesCasted.add(Integer.parseInt(s)); + } + + FpDataEntityManager em = new FpDataEntityManager(); + TreeSet fpsSorted = new TreeSet(); + + for(Integer counter : valuesCasted){ + fpsSorted.add(em.getExistingFPByCounter(counter)); + } + + //We retrieve the differences between the fp (t+1) and fo (t) + TreeSet fpsInversed = new TreeSet(); + fpsInversed = (TreeSet)fpsSorted.descendingSet(); + + HashMap differencesMap = new HashMap(); + Iterator it = fpsInversed.iterator(); + + //Initialisation for the first fingerprint + FpDataEntity fp0 = it.next(); + HashMap att0 = fp0.fpToHashMap(); + HashMap tabHtmlDifferences = new HashMap(); + int numberFp = fpsInversed.size(); + + while(it.hasNext()){ + FpDataEntity fp1 = it.next(); + HashMap att1 = fp1.fpToHashMap(); + + String diff = ""; + //We compare fp1 with fp0 + if (fp1.getUserAgentHttp() != null ? !fp1.getUserAgentHttp().equals(fp0.getUserAgentHttp()) : fp0.getUserAgentHttp() != null) diff +="userAgentHttp, "; + if (fp1.getAcceptHttp() != null ? !fp1.getAcceptHttp().equals(fp0.getAcceptHttp()) : fp0.getAcceptHttp() != null) diff += "acceptHttp, "; + if (fp1.getEncodingHttp() != null ? !fp1.getEncodingHttp().equals(fp0.getEncodingHttp()) : fp0.getEncodingHttp() != null) diff += "encodingHttp, "; + if (fp1.getLanguageHttp() != null ? !fp1.getLanguageHttp().equals(fp0.getLanguageHttp()) : fp0.getLanguageHttp() != null) diff += "languageHttp, "; + if (fp1.getAddressHttp() != null ? !fp1.getAddressHttp().equals(fp0.getAddressHttp()) : fp0.getAddressHttp() != null) diff += "addresse IP, "; + + if (fp1.getPluginsJs() != null ? !fp1.getPluginsJs().equals(fp0.getPluginsJs()) : fp0.getPluginsJs() != null) diff += "pluginsJs, "; + if (fp1.getPlatformJs() != null ? !fp1.getPlatformJs().equals(fp0.getPlatformJs()) : fp0.getPlatformJs() != null) diff += "platformJs, "; + if (fp1.getCookiesJs() != null ? !fp1.getCookiesJs().equals(fp0.getCookiesJs()) : fp0.getCookiesJs() != null) diff += "cookiesJs, "; + if (fp1.getDntJs() != null ? !fp1.getDntJs().equals(fp0.getDntJs()) : fp0.getDntJs() != null) diff += "dntJs, "; + if (fp1.getTimezoneJs() != null ? !fp1.getTimezoneJs().equals(fp0.getTimezoneJs()) : fp0.getTimezoneJs() != null) diff += "timezoneJs, "; + if (fp1.getResolutionJs() != null ? !fp1.getResolutionJs().equals(fp0.getResolutionJs()) : fp0.getResolutionJs() != null) diff +="resolutionJs, "; + if (fp1.getLocalJs() != null ? !fp1.getLocalJs().equals(fp0.getLocalJs()) : fp0.getLocalJs() != null) diff += "localJs, "; + if (fp1.getSessionJs() != null ? !fp1.getSessionJs().equals(fp0.getSessionJs()) : fp0.getSessionJs() != null) diff += "sessionJs, "; + if (fp1.getCanvasJs() != null ? !fp1.getCanvasJs().equals(fp0.getCanvasJs()) : fp0.getCanvasJs() != null) diff += "canvasJs, "; + //manque webgl vendor et renderer + + if (fp1.getFontsFlash() != null ? !fp1.getFontsFlash().equals(fp0.getFontsFlash()) : fp0.getFontsFlash() != null) diff += "fontsFlash, "; + if (fp1.getResolutionFlash() != null ? !fp1.getResolutionFlash().equals(fp0.getResolutionFlash()) : fp0.getResolutionFlash() != null) diff +="resolutionFlash, "; + if (fp1.getLanguageFlash() != null ? !fp1.getLanguageFlash().equals(fp0.getLanguageFlash()) : fp0.getLanguageFlash() != null) diff += "languageFlash, "; + if (fp1.getPlatformFlash() != null ? !fp1.getPlatformFlash().equals(fp0.getPlatformFlash()) : fp0.getPlatformFlash() != null) diff +="platformFlash, "; + + if (fp1.getAdBlock() != null ? !fp1.getAdBlock().equals(fp0.getAdBlock()) : fp0.getAdBlock() != null) diff += "adBlock, "; + + try{ + diff = diff.substring(0, diff.length()-2); + String[] attDiff = diff.split(","); + String rowValue = "date"+ DateFormatUtils.format(fp0.getTime(), "dd/MM/yyyy")+" - "+DateFormatUtils.format(fp0.getTime(), "HH")+"h"+DateFormatUtils.format(fp1.getTime(), "dd/MM/yyyy")+" - "+DateFormatUtils.format(fp1.getTime(), "HH")+"h"; + for(String att : attDiff){ + att = att.trim(); + if(!att.equals("canvasJs")){ + rowValue += ""+att+""+att0.get(att)+""+att1.get(att)+""; + }else{ + rowValue +=""+att+""; + } + } + tabHtmlDifferences.put(fp1.getCounter(), rowValue); + + differencesMap.put(fp1.getCounter(), diff); + fp0 = (FpDataEntity) fp1.clone(); + att0 = (HashMap) att1.clone(); + }catch(StringIndexOutOfBoundsException e){ + String rowValue = "date"+DateFormatUtils.format(fp0.getTime(), "dd/MM/yyyy")+" - "+DateFormatUtils.format(fp0.getTime(), "HH")+"h"+DateFormatUtils.format(fp1.getTime(), "dd/MM/yyyy")+" - "+DateFormatUtils.format(fp1.getTime(), "HH")+"h"; + tabHtmlDifferences.put(fp1.getCounter(), rowValue); + differencesMap.put(fp1.getCounter(), "nodiff"); + } + + } + + fpsSorted.remove(fpsSorted.last()); + return ok(differences.render(fpsSorted, differencesMap, tabHtmlDifferences)); + } + +} diff --git a/website/app/controllers/StatsController.java b/website/app/controllers/StatsController.java new file mode 100644 index 0000000..9b03fe7 --- /dev/null +++ b/website/app/controllers/StatsController.java @@ -0,0 +1,134 @@ +package controllers; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import models.CombinationStatsEntityManager; +import models.FpDataEntity; +import models.FpDataEntityManager; +import models.Stats; +import play.cache.Cache; +import play.libs.Crypto; +import play.libs.Json; +import play.mvc.Controller; +import play.mvc.Result; +import views.html.stats; + +import java.sql.Timestamp; +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Map; + + +public class StatsController extends Controller{ + + public static Result percentages(){ + String enc = request().body().asText(); + String clear = Crypto.decryptAES(enc); + JsonNode n = Json.parse(clear); + try { + Integer counter = n.get("c").asInt(); + FpDataEntityManager em = new FpDataEntityManager(); + FpDataEntity fp = em.getExistingFPByCounter(counter); + CombinationStatsEntityManager emc = new CombinationStatsEntityManager(); + ObjectNode node = (ObjectNode) Json.toJson(fp); + node.remove("counter"); + node.remove("octaneScore"); + node.remove("sunspiderTime"); + node.remove("addressHttp"); + node.remove("time"); + node.remove("hostHttp"); + node.remove("connectionHttp"); + node.remove("orderHttp"); + node.remove("ieDataJs"); + node.remove("id"); + node.remove("vendorWebGljs"); + node.remove("rendererWebGljs"); + node.remove("webGlJs"); + String pluginsJs = fp.getPluginsJs(); + node.remove("pluginsJs"); + node.remove("canvasJs"); + node.remove("fontsFlash"); + node.remove("webGLJsHashed"); + + JsonNode json = (JsonNode) node; + Map percentages = emc.getPercentages(json); + Map percentagesPlugins = emc.getPercentagesPlugins(pluginsJs); + + percentages.put("pluginsJs", percentages.get("pluginsJsHashed")); + percentages.put("canvasJs", percentages.get("canvasJsHashed")); + percentages.put("fontsFlash", percentages.get("fontsFlashHashed")); + percentages.remove("pluginsJsHashed"); + percentages.remove("canvasJsHashed"); + percentages.remove("fontsFlashHashed"); + + ObjectNode nodePer = (ObjectNode) Json.toJson(percentages); + + JsonNode jsonPerPlugins = Json.toJson(percentagesPlugins); + nodePer.put("perPluginsJs", jsonPerPlugins); + + json = (JsonNode) nodePer; + + return ok(json); + } catch (Exception e){ + return badRequest(); + } + } + + public static Result stats() throws Exception{ + return ok(Cache.getOrElse("stats-html", () -> { + Stats s = Stats.getInstance(); + return stats.render(s.getNbTotal(),Json.toJson(s.getTimezone()),Json.toJson(s.getBrowsers()), + Json.toJson(s.getOs()),Json.toJson(s.getLanguages()),Json.toJson(s.getNbFonts()),"","",""); + }, 60*60*3)); + } + + /* + Method called through a form on "stats" page + It receives 2 dates (upper and lower) + It reloads the page with new parameters for the charts + */ + public static Result statsTime(){ + Map vals = request().body().asFormUrlEncoded(); + String datelString = vals.get("datel")[0]; + String dateuString = vals.get("dateu")[0]; + String typeReq = vals.get("typereq")[0]; + + //We check if the information is already in cache + if((typeReq.equals("month") && Cache.get("monthStats") != null) || (typeReq.equals("week") && Cache.get("weekStats") != null)){ + Stats s = (Stats) Cache.get(typeReq+"Stats"); + + return ok(stats.render(s.getNbTotal(), Json.toJson(s.getTimezone()), Json.toJson(s.getBrowsers()), + Json.toJson(s.getOs()), Json.toJson(s.getLanguages()), Json.toJson(s.getNbFonts()), datelString, dateuString, typeReq)); + } + + //Only if custom or if the information is not in cache + try{ + DateFormat dateFormat = new SimpleDateFormat("dd-MM-yyyy"); + Date datel = dateFormat.parse(datelString); + Date dateu = dateFormat.parse(dateuString); + + Timestamp datelTs = new Timestamp(datel.getTime()); + Timestamp dateuTs = new Timestamp(dateu.getTime()); + + Stats s = new Stats(datelTs, dateuTs); + + //We check if the cache is empty + if(typeReq.equals("month") && Cache.get("monthStats") == null){ + Cache.set("monthStats", s, 60*60*24); + }else if(typeReq.equals("week") && Cache.get("weekStats") == null){ + Cache.set("weekStats", s, 60*60*12); + } + //The case for "all" is managed by controllers.stats + + return ok(stats.render(s.getNbTotal(),Json.toJson(s.getTimezone()),Json.toJson(s.getBrowsers()), + Json.toJson(s.getOs()),Json.toJson(s.getLanguages()),Json.toJson(s.getNbFonts()), datelString, dateuString, typeReq)); + + }catch(ParseException e){ + System.out.println("Parse exception : "+e); + } + + return redirect("/stats"); + } +} diff --git a/website/app/models/AudioEntity.java b/website/app/models/AudioEntity.java new file mode 100644 index 0000000..9194d1b --- /dev/null +++ b/website/app/models/AudioEntity.java @@ -0,0 +1,377 @@ +package models; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +@javax.persistence.Table(name = "audio", schema = "fingerprint", catalog = "") +public class AudioEntity { + private int counter; + + @Id + @javax.persistence.Column(name = "counter") + public int getCounter() { + return counter; + } + + public void setCounter(int counter) { + this.counter = counter; + } + + private String id; + + @Basic + @javax.persistence.Column(name = "id") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + private String acSampleRate; + + @Basic + @javax.persistence.Column(name = "acSampleRate") + public String getAcSampleRate() { + return acSampleRate; + } + + public void setAcSampleRate(String acSampleRate) { + this.acSampleRate = acSampleRate; + } + + private String acState; + + @Basic + @javax.persistence.Column(name = "acState") + public String getAcState() { + return acState; + } + + public void setAcState(String acState) { + this.acState = acState; + } + + private String acMaxChannelCount; + + @Basic + @javax.persistence.Column(name = "acMaxChannelCount") + public String getAcMaxChannelCount() { + return acMaxChannelCount; + } + + public void setAcMaxChannelCount(String acMaxChannelCount) { + this.acMaxChannelCount = acMaxChannelCount; + } + + private String acNumberOfInputs; + + @Basic + @javax.persistence.Column(name = "acNumberOfInputs") + public String getAcNumberOfInputs() { + return acNumberOfInputs; + } + + public void setAcNumberOfInputs(String acNumberOfInputs) { + this.acNumberOfInputs = acNumberOfInputs; + } + + private String acNumberOfOutputs; + + @Basic + @javax.persistence.Column(name = "acNumberOfOutputs") + public String getAcNumberOfOutputs() { + return acNumberOfOutputs; + } + + public void setAcNumberOfOutputs(String acNumberOfOutputs) { + this.acNumberOfOutputs = acNumberOfOutputs; + } + + private String acChannelCount; + + @Basic + @javax.persistence.Column(name = "acChannelCount") + public String getAcChannelCount() { + return acChannelCount; + } + + public void setAcChannelCount(String acChannelCount) { + this.acChannelCount = acChannelCount; + } + + private String acChannelCountMode; + + @Basic + @javax.persistence.Column(name = "acChannelCountMode") + public String getAcChannelCountMode() { + return acChannelCountMode; + } + + public void setAcChannelCountMode(String acChannelCountMode) { + this.acChannelCountMode = acChannelCountMode; + } + + private String acChannelInterpretation; + + @Basic + @javax.persistence.Column(name = "acChannelInterpretation") + public String getAcChannelInterpretation() { + return acChannelInterpretation; + } + + public void setAcChannelInterpretation(String acChannelInterpretation) { + this.acChannelInterpretation = acChannelInterpretation; + } + + private String anFftSize; + + @Basic + @javax.persistence.Column(name = "anFftSize") + public String getAnFftSize() { + return anFftSize; + } + + public void setAnFftSize(String anFftSize) { + this.anFftSize = anFftSize; + } + + private String anFrequencyBinCount; + + @Basic + @javax.persistence.Column(name = "anFrequencyBinCount") + public String getAnFrequencyBinCount() { + return anFrequencyBinCount; + } + + public void setAnFrequencyBinCount(String anFrequencyBinCount) { + this.anFrequencyBinCount = anFrequencyBinCount; + } + + private String anMinDecibels; + + @Basic + @javax.persistence.Column(name = "anMinDecibels") + public String getAnMinDecibels() { + return anMinDecibels; + } + + public void setAnMinDecibels(String anMinDecibels) { + this.anMinDecibels = anMinDecibels; + } + + private String anMaxDecibels; + + @Basic + @javax.persistence.Column(name = "anMaxDecibels") + public String getAnMaxDecibels() { + return anMaxDecibels; + } + + public void setAnMaxDecibels(String anMaxDecibels) { + this.anMaxDecibels = anMaxDecibels; + } + + private String anSmoothingTimeConstant; + + @Basic + @javax.persistence.Column(name = "anSmoothingTimeConstant") + public String getAnSmoothingTimeConstant() { + return anSmoothingTimeConstant; + } + + public void setAnSmoothingTimeConstant(String anSmoothingTimeConstant) { + this.anSmoothingTimeConstant = anSmoothingTimeConstant; + } + + private String anNumberOfInputs; + + @Basic + @javax.persistence.Column(name = "anNumberOfInputs") + public String getAnNumberOfInputs() { + return anNumberOfInputs; + } + + public void setAnNumberOfInputs(String anNumberOfInputs) { + this.anNumberOfInputs = anNumberOfInputs; + } + + private String anNumberOfOutputs; + + @Basic + @javax.persistence.Column(name = "anNumberOfOutputs") + public String getAnNumberOfOutputs() { + return anNumberOfOutputs; + } + + public void setAnNumberOfOutputs(String anNumberOfOutputs) { + this.anNumberOfOutputs = anNumberOfOutputs; + } + + private String anChannelCount; + + @Basic + @javax.persistence.Column(name = "anChannelCount") + public String getAnChannelCount() { + return anChannelCount; + } + + public void setAnChannelCount(String anChannelCount) { + this.anChannelCount = anChannelCount; + } + + private String anChannelCountMode; + + @Basic + @javax.persistence.Column(name = "anChannelCountMode") + public String getAnChannelCountMode() { + return anChannelCountMode; + } + + public void setAnChannelCountMode(String anChannelCountMode) { + this.anChannelCountMode = anChannelCountMode; + } + + private String anChannelInterpretation; + + @Basic + @javax.persistence.Column(name = "anChannelInterpretation") + public String getAnChannelInterpretation() { + return anChannelInterpretation; + } + + public void setAnChannelInterpretation(String anChannelInterpretation) { + this.anChannelInterpretation = anChannelInterpretation; + } + + private String audioDynSum; + + @Basic + @javax.persistence.Column(name = "audioDynSum") + public String getAudioDynSum() { + return audioDynSum; + } + + public void setAudioDynSum(String audioDynSum) { + this.audioDynSum = audioDynSum; + } + + private String audioDynHash; + + @Basic + @javax.persistence.Column(name = "audioDynHash") + public String getAudioDynHash() { + return audioDynHash; + } + + public void setAudioDynHash(String audioDynHash) { + this.audioDynHash = audioDynHash; + } + + private String audioPoints; + + @Basic + @javax.persistence.Column(name = "audioPoints") + public String getAudioPoints() { + return audioPoints; + } + + public void setAudioPoints(String audioPoints) { + this.audioPoints = audioPoints; + } + + private String audioDynPoints; + + @Basic + @javax.persistence.Column(name = "audioDynPoints") + public String getAudioDynPoints() { + return audioDynPoints; + } + + public void setAudioDynPoints(String audioDynPoints) { + this.audioDynPoints = audioDynPoints; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + AudioEntity that = (AudioEntity) o; + + if (counter != that.counter) return false; + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (acSampleRate != null ? !acSampleRate.equals(that.acSampleRate) : that.acSampleRate != null) return false; + if (acState != null ? !acState.equals(that.acState) : that.acState != null) return false; + if (acMaxChannelCount != null ? !acMaxChannelCount.equals(that.acMaxChannelCount) : that.acMaxChannelCount != null) + return false; + if (acNumberOfInputs != null ? !acNumberOfInputs.equals(that.acNumberOfInputs) : that.acNumberOfInputs != null) + return false; + if (acNumberOfOutputs != null ? !acNumberOfOutputs.equals(that.acNumberOfOutputs) : that.acNumberOfOutputs != null) + return false; + if (acChannelCount != null ? !acChannelCount.equals(that.acChannelCount) : that.acChannelCount != null) + return false; + if (acChannelCountMode != null ? !acChannelCountMode.equals(that.acChannelCountMode) : that.acChannelCountMode != null) + return false; + if (acChannelInterpretation != null ? !acChannelInterpretation.equals(that.acChannelInterpretation) : that.acChannelInterpretation != null) + return false; + if (anFftSize != null ? !anFftSize.equals(that.anFftSize) : that.anFftSize != null) return false; + if (anFrequencyBinCount != null ? !anFrequencyBinCount.equals(that.anFrequencyBinCount) : that.anFrequencyBinCount != null) + return false; + if (anMinDecibels != null ? !anMinDecibels.equals(that.anMinDecibels) : that.anMinDecibels != null) + return false; + if (anMaxDecibels != null ? !anMaxDecibels.equals(that.anMaxDecibels) : that.anMaxDecibels != null) + return false; + if (anSmoothingTimeConstant != null ? !anSmoothingTimeConstant.equals(that.anSmoothingTimeConstant) : that.anSmoothingTimeConstant != null) + return false; + if (anNumberOfInputs != null ? !anNumberOfInputs.equals(that.anNumberOfInputs) : that.anNumberOfInputs != null) + return false; + if (anNumberOfOutputs != null ? !anNumberOfOutputs.equals(that.anNumberOfOutputs) : that.anNumberOfOutputs != null) + return false; + if (anChannelCount != null ? !anChannelCount.equals(that.anChannelCount) : that.anChannelCount != null) + return false; + if (anChannelCountMode != null ? !anChannelCountMode.equals(that.anChannelCountMode) : that.anChannelCountMode != null) + return false; + if (anChannelInterpretation != null ? !anChannelInterpretation.equals(that.anChannelInterpretation) : that.anChannelInterpretation != null) + return false; + if (audioDynSum != null ? !audioDynSum.equals(that.audioDynSum) : that.audioDynSum != null) return false; + if (audioDynHash != null ? !audioDynHash.equals(that.audioDynHash) : that.audioDynHash != null) return false; + if (audioPoints != null ? !audioPoints.equals(that.audioPoints) : that.audioPoints != null) return false; + if (audioDynPoints != null ? !audioDynPoints.equals(that.audioDynPoints) : that.audioDynPoints != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = counter; + result = 31 * result + (id != null ? id.hashCode() : 0); + result = 31 * result + (acSampleRate != null ? acSampleRate.hashCode() : 0); + result = 31 * result + (acState != null ? acState.hashCode() : 0); + result = 31 * result + (acMaxChannelCount != null ? acMaxChannelCount.hashCode() : 0); + result = 31 * result + (acNumberOfInputs != null ? acNumberOfInputs.hashCode() : 0); + result = 31 * result + (acNumberOfOutputs != null ? acNumberOfOutputs.hashCode() : 0); + result = 31 * result + (acChannelCount != null ? acChannelCount.hashCode() : 0); + result = 31 * result + (acChannelCountMode != null ? acChannelCountMode.hashCode() : 0); + result = 31 * result + (acChannelInterpretation != null ? acChannelInterpretation.hashCode() : 0); + result = 31 * result + (anFftSize != null ? anFftSize.hashCode() : 0); + result = 31 * result + (anFrequencyBinCount != null ? anFrequencyBinCount.hashCode() : 0); + result = 31 * result + (anMinDecibels != null ? anMinDecibels.hashCode() : 0); + result = 31 * result + (anMaxDecibels != null ? anMaxDecibels.hashCode() : 0); + result = 31 * result + (anSmoothingTimeConstant != null ? anSmoothingTimeConstant.hashCode() : 0); + result = 31 * result + (anNumberOfInputs != null ? anNumberOfInputs.hashCode() : 0); + result = 31 * result + (anNumberOfOutputs != null ? anNumberOfOutputs.hashCode() : 0); + result = 31 * result + (anChannelCount != null ? anChannelCount.hashCode() : 0); + result = 31 * result + (anChannelCountMode != null ? anChannelCountMode.hashCode() : 0); + result = 31 * result + (anChannelInterpretation != null ? anChannelInterpretation.hashCode() : 0); + result = 31 * result + (audioDynSum != null ? audioDynSum.hashCode() : 0); + result = 31 * result + (audioDynHash != null ? audioDynHash.hashCode() : 0); + result = 31 * result + (audioPoints != null ? audioPoints.hashCode() : 0); + result = 31 * result + (audioDynPoints != null ? audioDynPoints.hashCode() : 0); + return result; + } +} diff --git a/website/app/models/AudioEntityManager.java b/website/app/models/AudioEntityManager.java new file mode 100644 index 0000000..ff046e6 --- /dev/null +++ b/website/app/models/AudioEntityManager.java @@ -0,0 +1,55 @@ +package models; + +import play.db.jpa.JPA; + +import javax.persistence.EntityManager; +import java.util.function.Function; + +public class AudioEntityManager { + + private A withTransaction(Function f) { + try { + return JPA.withTransaction(() -> f.apply(JPA.em())); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + public AudioEntity create(String id, String acSampleRate, String acState, String acMaxChannelCount, + String acNumberOfInputs, String acNumberOfOutputs, String acChannelCount, + String acChannelCountMode, String acChannelInterpretation, String anFftSize, + String anFrequencyBinCount, String anMinDecibels, String anMaxDecibels, + String anSmoothingTimeConstant, String anNumberOfInputs, String anNumberOfOutputs, + String anChannelCount, String anChannelCountMode, String anChannelInterpretation, + String audioDynSum, String audioDynHash, String audioPoints, String audioDynPoints){ + return withTransaction(em -> { + AudioEntity at = new AudioEntity(); + at.setId(id); + at.setAcSampleRate(acSampleRate); + at.setAcState(acState); + at.setAcMaxChannelCount(acMaxChannelCount); + at.setAcNumberOfInputs(acNumberOfInputs); + at.setAcNumberOfOutputs(acNumberOfOutputs); + at.setAcChannelCount(acChannelCount); + at.setAcChannelCountMode(acChannelCountMode); + at.setAcChannelInterpretation(acChannelInterpretation); + at.setAnFftSize(anFftSize); + at.setAnFrequencyBinCount(anFrequencyBinCount); + at.setAnMinDecibels(anMinDecibels); + at.setAnMaxDecibels(anMaxDecibels); + at.setAnSmoothingTimeConstant(anSmoothingTimeConstant); + at.setAnNumberOfInputs(anNumberOfInputs); + at.setAnNumberOfOutputs(anNumberOfOutputs); + at.setAnChannelCount(anChannelCount); + at.setAnChannelCountMode(anChannelCountMode); + at.setAnChannelInterpretation(anChannelInterpretation); + at.setAudioDynSum(audioDynSum); + at.setAudioDynHash(audioDynHash); + at.setAudioPoints(audioPoints); + at.setAudioDynPoints(audioDynPoints); + em.persist(at); + return at; + }); + } + +} diff --git a/website/app/models/BlockExtensionsEntity.java b/website/app/models/BlockExtensionsEntity.java new file mode 100644 index 0000000..34051f0 --- /dev/null +++ b/website/app/models/BlockExtensionsEntity.java @@ -0,0 +1,159 @@ +package models; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.Id; + +@Entity +@javax.persistence.Table(name = "blockExtensions", schema = "fingerprint", catalog = "") +public class BlockExtensionsEntity { + private int counter; + + @Id + @javax.persistence.Column(name = "counter") + public int getCounter() { + return counter; + } + + public void setCounter(int counter) { + this.counter = counter; + } + + private String id; + + @Basic + @javax.persistence.Column(name = "id") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + private String latest_links; + + @Basic + @javax.persistence.Column(name = "latest_links") + public String getLatestLinks() { + return latest_links; + } + + public void setLatestLinks(String latest_links) { + this.latest_links = latest_links; + } + + private String lowe_links; + + @Basic + @javax.persistence.Column(name = "lowe_links") + public String getLoweLinks() { + return lowe_links; + } + + public void setLoweLinks(String lowe_links) { + this.lowe_links = lowe_links; + } + + + private String old_links; + + @Basic + @javax.persistence.Column(name = "old_links") + public String getOldLinks() { + return old_links; + } + + public void setOldLinks(String old_links) { + this.old_links = old_links; + } + + + private String random_links; + + @Basic + @javax.persistence.Column(name = "random_links") + public String getRandomLinks() { + return random_links; + } + + public void setRandomLinks(String random_links) { + this.random_links = random_links; + } + + + + private String latest_results; + + @Basic + @javax.persistence.Column(name = "latest_results") + public String getLatestResults() { + return latest_results; + } + + public void setLatestResults(String latest_results) { + this.latest_results = latest_results; + } + + private String lowe_results; + + @Basic + @javax.persistence.Column(name = "lowe_results") + public String getLoweResults() { + return lowe_results; + } + + public void setLoweResults(String lowe_results) { + this.lowe_results = lowe_results; + } + + + private String old_results; + + @Basic + @javax.persistence.Column(name = "old_results") + public String getOldResults() { + return old_results; + } + + public void setOldResults(String old_results) { + this.old_results = old_results; + } + + + private String random_results; + + @Basic + @javax.persistence.Column(name = "random_results") + public String getRandomResults() { + return random_results; + } + + public void setRandomResults(String random_results) { + this.random_results = random_results; + } + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + BlockExtensionsEntity that = (BlockExtensionsEntity) o; + + if (counter != that.counter) return false; + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (latest_links != null ? !latest_links.equals(that.latest_links) : that.latest_links != null) return false; + if (lowe_links != null ? !lowe_links.equals(that.lowe_links) : that.lowe_links != null) return false; + if (old_links != null ? !old_links.equals(that.old_links) : that.old_links != null) return false; + if (random_links != null ? !random_links.equals(that.random_links) : that.random_links != null) return false; + if (latest_results != null ? !latest_results.equals(that.latest_results) : that.latest_results != null) return false; + if (lowe_results != null ? !lowe_results.equals(that.lowe_results) : that.lowe_results != null) return false; + if (old_results != null ? !old_results.equals(that.old_results) : that.old_results != null) return false; + if (random_results != null ? !random_results.equals(that.random_results) : that.random_results != null) return false; + + return true; + } + +} + diff --git a/website/app/models/BlockExtensionsEntityManager.java b/website/app/models/BlockExtensionsEntityManager.java new file mode 100644 index 0000000..08d2739 --- /dev/null +++ b/website/app/models/BlockExtensionsEntityManager.java @@ -0,0 +1,37 @@ +package models; + +import play.db.jpa.JPA; + +import javax.persistence.EntityManager; +import java.util.function.Function; + +public class BlockExtensionsEntityManager { + + private A withTransaction(Function f) { + try { + return JPA.withTransaction(() -> f.apply(JPA.em())); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + public BlockExtensionsEntity create(String id, String latest_links, String lowe_links, String old_links, + String random_links, String latest_results, String lowe_results, + String old_results, String random_results){ + return withTransaction(em -> { + BlockExtensionsEntity at = new BlockExtensionsEntity(); + at.setId(id); + at.setLatestLinks(latest_links); + at.setLoweLinks(lowe_links); + at.setOldLinks(old_links); + at.setRandomLinks(random_links); + at.setLatestResults(latest_results); + at.setLoweResults(lowe_results); + at.setOldResults(old_results); + at.setRandomResults(random_results); + em.persist(at); + return at; + }); + } + +} diff --git a/website/app/models/CombinationStatsEntity.java b/website/app/models/CombinationStatsEntity.java new file mode 100644 index 0000000..27ee3dd --- /dev/null +++ b/website/app/models/CombinationStatsEntity.java @@ -0,0 +1,98 @@ +package models; + +import javax.persistence.*; +import java.sql.Timestamp; + +/* + Entity bind to the table combinationStats + + We stock all the combination of all the indicators with their associated percentage + This table is update by making a POST request to /secret passing a parameter named secretKey + The method which executes the update is Application.controllers.updateCombinationStats() + The update is only made if the secretKey parameter is equal to the secretKey defined in secret/sk.txt + If you want to automatize this action you can use crontab + + You can use the following command line : + curl --data "secretKey=abcdefghijklmnopqrstuvwxyz" http://localhost:9000/secret http://localhost:9000/secret + + In our case, the key is equal to abcdefghijklmnopqrstuvwxyz and the website is hosted on localhost +*/ + +@Entity +@Table(name = "combinationStats", schema = "", catalog="fingerprint") +public class CombinationStatsEntity { + private int counter; + private String combination; + private String indicator; + private long number; + + @Id + @Column(name = "counter") + @GeneratedValue + public int getCounter() { + return counter; + } + + public void setCounter(int counter) { + this.counter = counter; + } + + public CombinationStatsEntity() { + } + + @Column(name = "combination") + public String getCombination() { + return combination; + } + + public void setCombination(String combination) { + this.combination = combination; + } + + @Basic + @Column(name = "indicator") + public String getIndicator() { + return indicator; + } + + public void setIndicator(String indicator) { + this.indicator = indicator; + } + + @Basic + @Column(name = "number") + public long getNumber() { + return number; + } + + public void setNumber(long number) { + this.number = number; + } + + + + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null) return false; + + CombinationStatsEntity that = (CombinationStatsEntity) o; + + if(that.getIndicator() != this.getIndicator()) return false; + if(that.getCombination() != this.getCombination()) return false; + if(that.getNumber() != this.getNumber()) return false; + + return true; + } + + @Override + public int hashCode() { + int result = 0; + result = 31 * result + (combination != null ? combination.hashCode() : 0); + result = 31 * result + (indicator != null ? indicator.hashCode() : 0); + + return result; + } + +} diff --git a/website/app/models/CombinationStatsEntityManager.java b/website/app/models/CombinationStatsEntityManager.java new file mode 100644 index 0000000..a5ab0f3 --- /dev/null +++ b/website/app/models/CombinationStatsEntityManager.java @@ -0,0 +1,340 @@ +package models; + +import com.fasterxml.jackson.databind.JsonNode; +import play.db.jpa.JPA; +import javax.persistence.EntityManager; +import javax.persistence.Query; +import java.sql.Timestamp; +import java.util.*; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + + +public class CombinationStatsEntityManager { + + private A withTransaction(Function f) { + try { + return JPA.withTransaction(() -> f.apply(JPA.em())); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + public CombinationStatsEntity createCombinationStats(String combination, String indicator, long number){ + return withTransaction(em -> { + CombinationStatsEntity cs = new CombinationStatsEntity(); + cs.setCombination(combination); + cs.setIndicator(indicator); + cs.setNumber(number); + + em.persist(cs); + return cs; + }); + } + + public int getNumberOfEntries(){ + String nbTotalQuery = "SELECT count(*) FROM CombinationStatsEntity"; + return withTransaction(em -> ((Long) em.createQuery(nbTotalQuery).getResultList().get(0)).intValue()); + } + + public int updateCombinationStats(String combination, String indicator, long number){ + String query = "UPDATE CombinationStatsEntity cs SET cs.number = :number WHERE cs.indicator = :indicator AND cs.combination = :combination"; + int counter = withTransaction(em -> (Integer)( em.createQuery(query) + .setParameter("number", number) + .setParameter("indicator", indicator) + .setParameter("combination", combination) + .executeUpdate())); + return counter; + } + + public Map getPercentages(JsonNode values) { + //Get the total number of entries in the database + String nbTotalQuery = "SELECT count(*) FROM FpDataEntity"; + double nbTotal = withTransaction(em -> ((Long) em.createQuery(nbTotalQuery).getResultList().get(0)).doubleValue()); + + String nbSameValueBaseQuery = "SELECT number FROM CombinationStatsEntity WHERE ";//Add attribute = value + HashMap percentage = new HashMap<>(); + Iterator it = values.fieldNames(); + while(it.hasNext()) { + String column = it.next(); + String nbSameValueQuery = nbSameValueBaseQuery+" combination = :col and indicator = :indic"; + long nbSameValue = (withTransaction(em -> { + Query q = em.createQuery(nbSameValueQuery) + .setParameter("col", values.get(column).asText().replace("\"", "'")) + .setParameter("indic",column); + + List l = q.getResultList(); + return (Long) l.get(0); + })); + + percentage.put(column, (nbSameValue/nbTotal)*100); + } + return percentage; + } + + public boolean testExistingCombination(String combi){ + String query = "SELECT count(*) FROM CombinationStatsEntity WHERE combination = :combi"; + int nbTotal = withTransaction(em -> ((Long) em.createQuery(query) + .setParameter("combi",combi) + .getResultList().get(0)).intValue()); + + if(nbTotal > 0){ + return true; + }else{ + return false; + } + } + + public Long getNbIdenticalPlugins(String plugin) { + String query = "SELECT count(*) FROM FpDataEntity WHERE pluginsJs LIKE :plugin"; + Long nbTotal = withTransaction(em -> ((Long) em.createQuery(query) + .setParameter("plugin","%"+plugin+"%") + .getResultList().get(0)).longValue()); + return nbTotal; + } + + public HashMap getPercentagesPlugins(String pluginsJs){ + + //Get the total number of entries in the database + String nbTotalQuery = "SELECT count(*) FROM FpDataEntity"; + double nbTotal = withTransaction(em -> ((Long) em.createQuery(nbTotalQuery).getResultList().get(0)).doubleValue()); + + String patternStringPlugin = "Plugin [0-9]+: ([a-zA-Z -.]+)"; + Pattern pattern = Pattern.compile(patternStringPlugin); + Matcher matcher = pattern.matcher(pluginsJs); + + HashMap percentage = new HashMap<>(); + String query = "SELECT number FROM CombinationStatsEntity WHERE indicator='pluginsJs' AND combination = :combination"; + + while(matcher.find()) { + String plugin = matcher.group(1); + if(plugin !=null){ + double percSameValue = (withTransaction(em -> ((Long) em.createQuery(query) + .setParameter("combination",plugin) + .getResultList().get(0)).intValue()))/nbTotal; + + percentage.put(plugin, percSameValue*100); + } + } + return percentage; + } + + public void updateCombinationStats(String userAgentHttp, String acceptHttp, String connectionHttp, + String encodingHttp, String languageHttp, String orderHttp, String pluginsJs, String platformJs, String cookiesJs, + String dntJs, String timezoneJs, String resolutionJs, String localJs, String sessionJs, String IEDataJs, + String resolutionFlash, String languageFlash, String platformFlash, + String adBlock, String pluginsJsHashed, String canvasJsHashed, String fontsFlashHashed){ + + //UserAgentHtpp + String query = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='userAgentHttp' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query) + .setParameter("combination", userAgentHttp) + .executeUpdate()))) == 0) { + createCombinationStats(userAgentHttp, "userAgentHttp", 1); + } + + //AcceptHttp + String query2 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='acceptHttp' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query2) + .setParameter("combination", acceptHttp) + .executeUpdate()))) == 0) { + createCombinationStats(acceptHttp, "acceptHttp", 1); + } + + //connectionHttp + String query3 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='connectionHttp' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query3) + .setParameter("combination", connectionHttp) + .executeUpdate()))) == 0) { + createCombinationStats(connectionHttp, "connectionHttp", 1); + } + + //encodingHttp + String query4 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='encodingHttp' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query4) + .setParameter("combination", encodingHttp) + .executeUpdate()))) == 0) { + createCombinationStats(encodingHttp, "encodingHttp", 1); + } + + //languageHttp + String query5 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='languageHttp' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query5) + .setParameter("combination", languageHttp) + .executeUpdate()))) == 0) { + createCombinationStats(languageHttp, "languageHttp", 1); + } + + //orderHttp + String query6 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='orderHttp' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query6) + .setParameter("combination", orderHttp) + .executeUpdate()))) == 0) { + createCombinationStats(orderHttp, "orderHttp", 1); + } + + //platformJs + String query7 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='platformJs' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query7) + .setParameter("combination", platformJs) + .executeUpdate()))) == 0) { + createCombinationStats(platformJs, "platformJs", 1); + } + + if(!platformJs.equals("no JS")) { + + //cookiesJs + String query8 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='cookiesJs' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query8) + .setParameter("combination", cookiesJs) + .executeUpdate()))) == 0) { + createCombinationStats(cookiesJs, "cookiesJs", 1); + } + + //dntJs + String query9 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='dntJs' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query9) + .setParameter("combination", dntJs) + .executeUpdate()))) == 0) { + createCombinationStats(dntJs, "dntJs", 1); + } + + //timezoneJs + String query10 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='timezoneJs' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query10) + .setParameter("combination", timezoneJs) + .executeUpdate()))) == 0) { + createCombinationStats(timezoneJs, "timezoneJs", 1); + } + + //resolutionJs + String query11 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='resolutionJs' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query11) + .setParameter("combination", resolutionJs) + .executeUpdate()))) == 0) { + createCombinationStats(resolutionJs, "resolutionJs", 1); + } + + //localJs + String query12 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='localJs' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query12) + .setParameter("combination", localJs) + .executeUpdate()))) == 0) { + createCombinationStats(localJs, "localJs", 1); + } + + //sessionJs + String query13 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='sessionJs' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query13) + .setParameter("combination", sessionJs) + .executeUpdate()))) == 0) { + createCombinationStats(sessionJs, "sessionJs", 1); + } + + //IEDataJs + String query14 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='IEDataJs' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query14) + .setParameter("combination", IEDataJs) + .executeUpdate()))) == 0) { + createCombinationStats(IEDataJs, "IEDataJs", 1); + } + + //resolutionFlash + String query15 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='resolutionFlash' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query15) + .setParameter("combination", resolutionFlash) + .executeUpdate()))) == 0) { + createCombinationStats(resolutionFlash, "resolutionFlash", 1); + } + + //languageFlash + String query16 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='languageFlash' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query16) + .setParameter("combination", languageFlash) + .executeUpdate()))) == 0) { + createCombinationStats(languageFlash, "languageFlash", 1); + } + + //platformFlash + String query17 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='platformFlash' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query17) + .setParameter("combination", platformFlash) + .executeUpdate()))) == 0) { + createCombinationStats(platformFlash, "platformFlash", 1); + } + + //adBlock + String query18 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='adBlock' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query18) + .setParameter("combination", adBlock) + .executeUpdate()))) == 0) { + createCombinationStats(adBlock, "adBlock", 1); + } + + //pluginsJsHashed + String query19 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='pluginsJsHashed' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query19) + .setParameter("combination", pluginsJsHashed) + .executeUpdate()))) == 0) { + createCombinationStats(pluginsJsHashed, "pluginsJsHashed", 1); + } + + //canvasJsHashed + String query20 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='canvasJsHashed' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query20) + .setParameter("combination", canvasJsHashed) + .executeUpdate()))) == 0) { + createCombinationStats(canvasJsHashed, "canvasJsHashed", 1); + } + + //fontsFlashHashed + String query21 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='fontsFlashHashed' AND combination = :combination"; + if ((withTransaction(em -> (Integer) (em.createQuery(query21) + .setParameter("combination", fontsFlashHashed) + .executeUpdate()))) == 0) { + createCombinationStats(fontsFlashHashed, "fontsFlashHashed", 1); + } + + String query22 = "UPDATE CombinationStatsEntity SET number = number +1 WHERE indicator='pluginsJs' AND combination = :combination"; + String patternStringPlugin = "Plugin [0-9]+: ([a-zA-Z -.]+)"; + Pattern pattern = Pattern.compile(patternStringPlugin); + + Matcher matcher = pattern.matcher(pluginsJs); + + while (matcher.find()) { + String plugin = matcher.group(1); + if (plugin != null) { + if ((withTransaction(em -> (Integer) (em.createQuery(query22) + .setParameter("combination", plugin) + .executeUpdate()))) == 0) { + createCombinationStats(plugin, "pluginsJs", 1); + } + } else { + if ((withTransaction(em -> (Integer) (em.createQuery(query22) + .setParameter("combination", pluginsJs) + .executeUpdate()))) == 0) { + createCombinationStats(pluginsJs, "pluginsJs", 1); + } + } + } + + } else { + //With no JS + //Bug FIX: we retrive the number of no JS values from the platform attribute + String platQuery = "SELECT number from CombinationStatsEntity WHERE indicator='platformJs' AND combination='no JS'"; + int nbNoJS = withTransaction(em -> ((Long) em.createQuery(platQuery).getResultList().get(0)).intValue()); + String[] attributes = {"cookiesJs","dntJs","timezoneJs","resolutionJs","localJs","sessionJs","IEDataJs","resolutionFlash", + "languageFlash","platformFlash","adBlock","pluginsJsHashed","canvasJsHashed","fontsFlashHashed", "pluginsJS"}; + for(String att: attributes){ + String queryNoJS = "UPDATE CombinationStatsEntity SET number = "+nbNoJS+" WHERE indicator='"+att+"' AND combination = 'no JS'"; + if ((withTransaction(em -> (Integer) (em.createQuery(queryNoJS).executeUpdate()))) == 0) { + createCombinationStats(canvasJsHashed, att, nbNoJS); + } + } + } + + } + + +} diff --git a/website/app/models/CounterMap.java b/website/app/models/CounterMap.java new file mode 100644 index 0000000..6eda38c --- /dev/null +++ b/website/app/models/CounterMap.java @@ -0,0 +1,38 @@ +package models; + +import java.util.Comparator; +import java.util.TreeMap; +import java.util.concurrent.atomic.AtomicInteger; + +public class CounterMap extends TreeMap{ + + public CounterMap(){ + super(); + } + + public CounterMap(Comparator comp){ + super(comp); + } + + public void add(String value){ + if(this.containsKey(value)){ + this.get(value).incrementAndGet(); + } else { + this.put(value, new AtomicInteger(1)); + } + } + + public void add(String key, String value){ + if(!this.containsKey(key)){ + this.put(key, new AtomicInteger(Integer.parseInt(value))); + } + } + + public double getCounter(){ + double counter = 0; + for(AtomicInteger value : this.values()){ + counter += value.doubleValue(); + } + return counter; + } +} diff --git a/website/app/models/ExtensionDataEntity.java b/website/app/models/ExtensionDataEntity.java new file mode 100644 index 0000000..039defe --- /dev/null +++ b/website/app/models/ExtensionDataEntity.java @@ -0,0 +1,969 @@ +package models; + +import javax.persistence.Basic; +import javax.persistence.Entity; +import javax.persistence.Id; +import java.sql.Timestamp; +import java.text.SimpleDateFormat; +import java.util.HashMap; + +@Entity +@javax.persistence.Table(name = "extensionData", schema = "", catalog = "fingerprint") +public class ExtensionDataEntity implements Comparable, Cloneable { + private int counter; + + @Id + @javax.persistence.Column(name = "counter") + public int getCounter() { + return counter; + } + + public void setCounter(int counter) { + this.counter = counter; + } + + private String id; + + @Basic + @javax.persistence.Column(name = "id") + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + private String addressHttp; + + @Basic + @javax.persistence.Column(name = "addressHttp") + public String getAddressHttp() { + return addressHttp; + } + + public void setAddressHttp(String addressHttp) { + this.addressHttp = addressHttp; + } + + private Timestamp creationDate; + + @Basic + @javax.persistence.Column(name = "creationDate") + public Timestamp getCreationDate() { + return creationDate; + } + + public void setCreationDate(Timestamp creationDate) { + this.creationDate = creationDate; + } + + private Timestamp updateDate; + + @Basic + @javax.persistence.Column(name = "updateDate") + public Timestamp getUpdateDate() { + return updateDate; + } + + public void setUpdateDate(Timestamp updateDate) { + this.updateDate = updateDate; + } + + private Timestamp endDate; + + @Basic + @javax.persistence.Column(name = "endDate") + public Timestamp getEndDate() { + return endDate; + } + + public void setEndDate(Timestamp endDate) { + this.endDate = endDate; + } + + private String userAgentHttp; + + @Basic + @javax.persistence.Column(name = "userAgentHttp") + public String getUserAgentHttp() { + return userAgentHttp; + } + + public void setUserAgentHttp(String userAgentHttp) { + this.userAgentHttp = userAgentHttp; + } + + private String acceptHttp; + + @Basic + @javax.persistence.Column(name = "acceptHttp") + public String getAcceptHttp() { + return acceptHttp; + } + + public void setAcceptHttp(String acceptHttp) { + this.acceptHttp = acceptHttp; + } + + private String hostHttp; + + @Basic + @javax.persistence.Column(name = "hostHttp") + public String getHostHttp() { + return hostHttp; + } + + public void setHostHttp(String hostHttp) { + this.hostHttp = hostHttp; + } + + private String connectionHttp; + + @Basic + @javax.persistence.Column(name = "connectionHttp") + public String getConnectionHttp() { + return connectionHttp; + } + + public void setConnectionHttp(String connectionHttp) { + this.connectionHttp = connectionHttp; + } + + private String encodingHttp; + + @Basic + @javax.persistence.Column(name = "encodingHttp") + public String getEncodingHttp() { + return encodingHttp; + } + + public void setEncodingHttp(String encodingHttp) { + this.encodingHttp = encodingHttp; + } + + private String languageHttp; + + @Basic + @javax.persistence.Column(name = "languageHttp") + public String getLanguageHttp() { + return languageHttp; + } + + public void setLanguageHttp(String languageHttp) { + this.languageHttp = languageHttp; + } + + private String orderHttp; + + @Basic + @javax.persistence.Column(name = "orderHttp") + public String getOrderHttp() { + return orderHttp; + } + + public void setOrderHttp(String orderHttp) { + this.orderHttp = orderHttp; + } + + private String pluginsJs; + + @Basic + @javax.persistence.Column(name = "pluginsJS") + public String getPluginsJs() { + return pluginsJs; + } + + public void setPluginsJs(String pluginsJs) { + this.pluginsJs = pluginsJs; + } + + private String platformJs; + + @Basic + @javax.persistence.Column(name = "platformJS") + public String getPlatformJs() { + return platformJs; + } + + public void setPlatformJs(String platformJs) { + this.platformJs = platformJs; + } + + private String cookiesJs; + + @Basic + @javax.persistence.Column(name = "cookiesJS") + public String getCookiesJs() { + return cookiesJs; + } + + public void setCookiesJs(String cookiesJs) { + this.cookiesJs = cookiesJs; + } + + private String dntJs; + + @Basic + @javax.persistence.Column(name = "dntJS") + public String getDntJs() { + return dntJs; + } + + public void setDntJs(String dntJs) { + this.dntJs = dntJs; + } + + private String timezoneJs; + + @Basic + @javax.persistence.Column(name = "timezoneJS") + public String getTimezoneJs() { + return timezoneJs; + } + + public void setTimezoneJs(String timezoneJs) { + this.timezoneJs = timezoneJs; + } + + private String resolutionJs; + + @Basic + @javax.persistence.Column(name = "resolutionJS") + public String getResolutionJs() { + return resolutionJs; + } + + public void setResolutionJs(String resolutionJs) { + this.resolutionJs = resolutionJs; + } + + private String localJs; + + @Basic + @javax.persistence.Column(name = "localJS") + public String getLocalJs() { + return localJs; + } + + public void setLocalJs(String localJs) { + this.localJs = localJs; + } + + private String sessionJs; + + @Basic + @javax.persistence.Column(name = "sessionJS") + public String getSessionJs() { + return sessionJs; + } + + public void setSessionJs(String sessionJs) { + this.sessionJs = sessionJs; + } + + private String ieDataJs; + + @Basic + @javax.persistence.Column(name = "IEDataJS") + public String getIeDataJs() { + return ieDataJs; + } + + public void setIeDataJs(String ieDataJs) { + this.ieDataJs = ieDataJs; + } + + private String canvasJs; + + @Basic + @javax.persistence.Column(name = "canvasJS") + public String getCanvasJs() { + return canvasJs; + } + + public void setCanvasJs(String canvasJs) { + this.canvasJs = canvasJs; + } + + private String webGlJs; + + @Basic + @javax.persistence.Column(name = "webGLJs") + public String getWebGlJs() { + return webGlJs; + } + + public void setWebGlJs(String webGlJs) { + this.webGlJs = webGlJs; + } + + private String fontsFlash; + + @Basic + @javax.persistence.Column(name = "fontsFlash") + public String getFontsFlash() { + return fontsFlash; + } + + public void setFontsFlash(String fontsFlash) { + this.fontsFlash = fontsFlash; + } + + private String resolutionFlash; + + @Basic + @javax.persistence.Column(name = "resolutionFlash") + public String getResolutionFlash() { + return resolutionFlash; + } + + public void setResolutionFlash(String resolutionFlash) { + this.resolutionFlash = resolutionFlash; + } + + private String languageFlash; + + @Basic + @javax.persistence.Column(name = "languageFlash") + public String getLanguageFlash() { + return languageFlash; + } + + public void setLanguageFlash(String languageFlash) { + this.languageFlash = languageFlash; + } + + private String platformFlash; + + @Basic + @javax.persistence.Column(name = "platformFlash") + public String getPlatformFlash() { + return platformFlash; + } + + public void setPlatformFlash(String platformFlash) { + this.platformFlash = platformFlash; + } + + private String adBlock; + + @Basic + @javax.persistence.Column(name = "adBlock") + public String getAdBlock() { + return adBlock; + } + + public void setAdBlock(String adBlock) { + this.adBlock = adBlock; + } + + private String vendorWebGljs; + + @Basic + @javax.persistence.Column(name = "vendorWebGLJS") + public String getVendorWebGljs() { + return vendorWebGljs; + } + + public void setVendorWebGljs(String vendorWebGljs) { + this.vendorWebGljs = vendorWebGljs; + } + + private String rendererWebGljs; + + @Basic + @javax.persistence.Column(name = "rendererWebGLJS") + public String getRendererWebGljs() { + return rendererWebGljs; + } + + public void setRendererWebGljs(String rendererWebGljs) { + this.rendererWebGljs = rendererWebGljs; + } + + private String octaneScore; + + @Basic + @javax.persistence.Column(name = "octaneScore") + public String getOctaneScore() { + return octaneScore; + } + + public void setOctaneScore(String octaneScore) { + this.octaneScore = octaneScore; + } + + private String sunspiderTime; + + @Basic + @javax.persistence.Column(name = "sunspiderTime") + public String getSunspiderTime() { + return sunspiderTime; + } + + public void setSunspiderTime(String sunspiderTime) { + this.sunspiderTime = sunspiderTime; + } + + private String pluginsJsHashed; + + @Basic + @javax.persistence.Column(name = "pluginsJSHashed") + public String getPluginsJsHashed() { + return pluginsJsHashed; + } + + public void setPluginsJsHashed(String pluginsJsHashed) { + this.pluginsJsHashed = pluginsJsHashed; + } + + private String canvasJsHashed; + + @Basic + @javax.persistence.Column(name = "canvasJSHashed") + public String getCanvasJsHashed() { + return canvasJsHashed; + } + + public void setCanvasJsHashed(String canvasJsHashed) { + this.canvasJsHashed = canvasJsHashed; + } + + private String webGlJsHashed; + + @Basic + @javax.persistence.Column(name = "webGLJsHashed") + public String getWebGlJsHashed() { + return webGlJsHashed; + } + + public void setWebGlJsHashed(String webGlJsHashed) { + this.webGlJsHashed = webGlJsHashed; + } + + private String fontsFlashHashed; + + @Basic + @javax.persistence.Column(name = "fontsFlashHashed") + public String getFontsFlashHashed() { + return fontsFlashHashed; + } + + public void setFontsFlashHashed(String fontsFlashHashed) { + this.fontsFlashHashed = fontsFlashHashed; + } + + private String hardwareConcurrency; + + @Basic + @javax.persistence.Column(name = "hardwareConcurrency") + public String getHardwareConcurrency() { + return hardwareConcurrency; + } + + public void setHardwareConcurrency(String hardwareConcurrency) { + this.hardwareConcurrency = hardwareConcurrency; + } + + private String availableScreenResolution; + + @Basic + @javax.persistence.Column(name = "availableScreenResolution") + public String getAvailableScreenResolution() { + return availableScreenResolution; + } + + public void setAvailableScreenResolution(String availableScreenResolution) { + this.availableScreenResolution = availableScreenResolution; + } + + private String cpuClass; + + @Basic + @javax.persistence.Column(name = "cpuClass") + public String getCpuClass() { + return cpuClass; + } + + public void setCpuClass(String cpuClass) { + this.cpuClass = cpuClass; + } + + private String modernizr; + + @Basic + @javax.persistence.Column(name = "modernizr") + public String getModernizr() { + return modernizr; + } + + public void setModernizr(String modernizr) { + this.modernizr = modernizr; + } + + private String overwrittenObjects; + + @Basic + @javax.persistence.Column(name = "overwrittenObjects") + public String getOverwrittenObjects() { + return overwrittenObjects; + } + + public void setOverwrittenObjects(String overwrittenObjects) { + this.overwrittenObjects = overwrittenObjects; + } + + private String osMediaQueries; + + @Basic + @javax.persistence.Column(name = "osMediaQueries") + public String getOsMediaQueries() { + return osMediaQueries; + } + + public void setOsMediaQueries(String osMediaQueries) { + this.osMediaQueries = osMediaQueries; + } + + private String appCodeName; + + @Basic + @javax.persistence.Column(name = "appCodeName") + public String getAppCodeName() { + return appCodeName; + } + + public void setAppCodeName(String appCodeName) { + this.appCodeName = appCodeName; + } + + private String oscpu; + + @Basic + @javax.persistence.Column(name = "oscpu") + public String getOscpu() { + return oscpu; + } + + public void setOscpu(String oscpu) { + this.oscpu = oscpu; + } + + private String appName; + + @Basic + @javax.persistence.Column(name = "appName") + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + private String appVersion; + + @Basic + @javax.persistence.Column(name = "appVersion") + public String getAppVersion() { + return appVersion; + } + + public void setAppVersion(String appVersion) { + this.appVersion = appVersion; + } + + private String languages; + + @Basic + @javax.persistence.Column(name = "languages") + public String getLanguages() { + return languages; + } + + public void setLanguages(String languages) { + this.languages = languages; + } + + private String mimeTypes; + + @Basic + @javax.persistence.Column(name = "mimeTypes") + public String getMimeTypes() { + return mimeTypes; + } + + public void setMimeTypes(String mimeTypes) { + this.mimeTypes = mimeTypes; + } + + private String pluginsUsingMimeTypes; + + @Basic + @javax.persistence.Column(name = "pluginsUsingMimeTypes") + public String getPluginsUsingMimeTypes() { + return pluginsUsingMimeTypes; + } + + public void setPluginsUsingMimeTypes(String pluginsUsingMimeTypes) { + this.pluginsUsingMimeTypes = pluginsUsingMimeTypes; + } + + private String product; + + @Basic + @javax.persistence.Column(name = "product") + public String getProduct() { + return product; + } + + public void setProduct(String product) { + this.product = product; + } + + private String productSub; + + @Basic + @javax.persistence.Column(name = "productSub") + public String getProductSub() { + return productSub; + } + + public void setProductSub(String productSub) { + this.productSub = productSub; + } + + private String vendor; + + @Basic + @javax.persistence.Column(name = "vendor") + public String getVendor() { + return vendor; + } + + public void setVendor(String vendor) { + this.vendor = vendor; + } + + private String vendorSub; + + @Basic + @javax.persistence.Column(name = "vendorSub") + public String getVendorSub() { + return vendorSub; + } + + public void setVendorSub(String vendorSub) { + this.vendorSub = vendorSub; + } + + private String touchSupport; + + @Basic + @javax.persistence.Column(name = "touchSupport") + public String getTouchSupport() { + return touchSupport; + } + + public void setTouchSupport(String touchSupport) { + this.touchSupport = touchSupport; + } + + private String buildID; + + @Basic + @javax.persistence.Column(name = "buildID") + public String getBuildID() { + return buildID; + } + + public void setBuildID(String buildID) { + this.buildID = buildID; + } + + private String navigatorPrototype; + + @Basic + @javax.persistence.Column(name = "navigatorPrototype") + public String getNavigatorPrototype() { + return navigatorPrototype; + } + + public void setNavigatorPrototype(String navigatorPrototype) { + this.navigatorPrototype = navigatorPrototype; + } + + private String mathsConstants; + + @Basic + @javax.persistence.Column(name = "mathsConstants") + public String getMathsConstants() { + return mathsConstants; + } + + public void setMathsConstants(String mathsConstants) { + this.mathsConstants = mathsConstants; + } + + private String resOverflow; + + @Basic + @javax.persistence.Column(name = "resOverflow") + public String getResOverflow() { + return resOverflow; + } + + public void setResOverflow(String resOverflow) { + this.resOverflow = resOverflow; + } + + private String errorsGenerated; + + @Basic + @javax.persistence.Column(name = "errorsGenerated") + public String getErrorsGenerated() { + return errorsGenerated; + } + + public void setErrorsGenerated(String errorsGenerated) { + this.errorsGenerated = errorsGenerated; + } + + private String unknownImageError; + + @Basic + @javax.persistence.Column(name = "unknownImageError") + public String getUnknownImageError() { + return unknownImageError; + } + + public void setUnknownImageError(String unknownImageError) { + this.unknownImageError = unknownImageError; + } + + private String fontsEnum; + + @Basic + @javax.persistence.Column(name = "fontsEnum") + public String getFontsEnum() { + return fontsEnum; + } + + public void setFontsEnum(String fontsEnum) { + this.fontsEnum = fontsEnum; + } + + private String audio; + + @Basic + @javax.persistence.Column(name = "audio") + public String getAudio() { + return audio; + } + + public void setAudio(String audio) { + this.audio = audio; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ExtensionDataEntity that = (ExtensionDataEntity) o; + + if (counter != that.counter) return false; + if (id != null ? !id.equals(that.id) : that.id != null) return false; + if (addressHttp != null ? !addressHttp.equals(that.addressHttp) : that.addressHttp != null) return false; + if (creationDate != null ? !creationDate.equals(that.creationDate) : that.creationDate != null) return false; + if (updateDate != null ? !updateDate.equals(that.updateDate) : that.updateDate != null) return false; + if (endDate != null ? !endDate.equals(that.endDate) : that.endDate != null) return false; + if (userAgentHttp != null ? !userAgentHttp.equals(that.userAgentHttp) : that.userAgentHttp != null) + return false; + if (acceptHttp != null ? !acceptHttp.equals(that.acceptHttp) : that.acceptHttp != null) return false; + if (hostHttp != null ? !hostHttp.equals(that.hostHttp) : that.hostHttp != null) return false; + if (connectionHttp != null ? !connectionHttp.equals(that.connectionHttp) : that.connectionHttp != null) + return false; + if (encodingHttp != null ? !encodingHttp.equals(that.encodingHttp) : that.encodingHttp != null) return false; + if (languageHttp != null ? !languageHttp.equals(that.languageHttp) : that.languageHttp != null) return false; + if (orderHttp != null ? !orderHttp.equals(that.orderHttp) : that.orderHttp != null) return false; + if (pluginsJs != null ? !pluginsJs.equals(that.pluginsJs) : that.pluginsJs != null) return false; + if (platformJs != null ? !platformJs.equals(that.platformJs) : that.platformJs != null) return false; + if (cookiesJs != null ? !cookiesJs.equals(that.cookiesJs) : that.cookiesJs != null) return false; + if (dntJs != null ? !dntJs.equals(that.dntJs) : that.dntJs != null) return false; + if (timezoneJs != null ? !timezoneJs.equals(that.timezoneJs) : that.timezoneJs != null) return false; + if (resolutionJs != null ? !resolutionJs.equals(that.resolutionJs) : that.resolutionJs != null) return false; + if (localJs != null ? !localJs.equals(that.localJs) : that.localJs != null) return false; + if (sessionJs != null ? !sessionJs.equals(that.sessionJs) : that.sessionJs != null) return false; + if (ieDataJs != null ? !ieDataJs.equals(that.ieDataJs) : that.ieDataJs != null) return false; + if (canvasJs != null ? !canvasJs.equals(that.canvasJs) : that.canvasJs != null) return false; + if (webGlJs != null ? !webGlJs.equals(that.webGlJs) : that.webGlJs != null) return false; + if (fontsFlash != null ? !fontsFlash.equals(that.fontsFlash) : that.fontsFlash != null) return false; + if (resolutionFlash != null ? !resolutionFlash.equals(that.resolutionFlash) : that.resolutionFlash != null) + return false; + if (languageFlash != null ? !languageFlash.equals(that.languageFlash) : that.languageFlash != null) + return false; + if (platformFlash != null ? !platformFlash.equals(that.platformFlash) : that.platformFlash != null) + return false; + if (adBlock != null ? !adBlock.equals(that.adBlock) : that.adBlock != null) return false; + if (vendorWebGljs != null ? !vendorWebGljs.equals(that.vendorWebGljs) : that.vendorWebGljs != null) + return false; + if (rendererWebGljs != null ? !rendererWebGljs.equals(that.rendererWebGljs) : that.rendererWebGljs != null) + return false; + if (octaneScore != null ? !octaneScore.equals(that.octaneScore) : that.octaneScore != null) return false; + if (sunspiderTime != null ? !sunspiderTime.equals(that.sunspiderTime) : that.sunspiderTime != null) + return false; + if (pluginsJsHashed != null ? !pluginsJsHashed.equals(that.pluginsJsHashed) : that.pluginsJsHashed != null) + return false; + if (canvasJsHashed != null ? !canvasJsHashed.equals(that.canvasJsHashed) : that.canvasJsHashed != null) + return false; + if (webGlJsHashed != null ? !webGlJsHashed.equals(that.webGlJsHashed) : that.webGlJsHashed != null) + return false; + if (fontsFlashHashed != null ? !fontsFlashHashed.equals(that.fontsFlashHashed) : that.fontsFlashHashed != null) + return false; + + return true; + } + + @Override + public int hashCode() { + int result = counter; + result = 31 * result + (id != null ? id.hashCode() : 0); + result = 31 * result + (addressHttp != null ? addressHttp.hashCode() : 0); + result = 31 * result + (creationDate != null ? creationDate.hashCode() : 0); + result = 31 * result + (updateDate != null ? updateDate.hashCode() : 0); + result = 31 * result + (endDate != null ? endDate.hashCode() : 0); + result = 31 * result + (userAgentHttp != null ? userAgentHttp.hashCode() : 0); + result = 31 * result + (acceptHttp != null ? acceptHttp.hashCode() : 0); + result = 31 * result + (hostHttp != null ? hostHttp.hashCode() : 0); + result = 31 * result + (connectionHttp != null ? connectionHttp.hashCode() : 0); + result = 31 * result + (encodingHttp != null ? encodingHttp.hashCode() : 0); + result = 31 * result + (languageHttp != null ? languageHttp.hashCode() : 0); + result = 31 * result + (orderHttp != null ? orderHttp.hashCode() : 0); + result = 31 * result + (pluginsJs != null ? pluginsJs.hashCode() : 0); + result = 31 * result + (platformJs != null ? platformJs.hashCode() : 0); + result = 31 * result + (cookiesJs != null ? cookiesJs.hashCode() : 0); + result = 31 * result + (dntJs != null ? dntJs.hashCode() : 0); + result = 31 * result + (timezoneJs != null ? timezoneJs.hashCode() : 0); + result = 31 * result + (resolutionJs != null ? resolutionJs.hashCode() : 0); + result = 31 * result + (localJs != null ? localJs.hashCode() : 0); + result = 31 * result + (sessionJs != null ? sessionJs.hashCode() : 0); + result = 31 * result + (ieDataJs != null ? ieDataJs.hashCode() : 0); + result = 31 * result + (canvasJs != null ? canvasJs.hashCode() : 0); + result = 31 * result + (webGlJs != null ? webGlJs.hashCode() : 0); + result = 31 * result + (fontsFlash != null ? fontsFlash.hashCode() : 0); + result = 31 * result + (resolutionFlash != null ? resolutionFlash.hashCode() : 0); + result = 31 * result + (languageFlash != null ? languageFlash.hashCode() : 0); + result = 31 * result + (platformFlash != null ? platformFlash.hashCode() : 0); + result = 31 * result + (adBlock != null ? adBlock.hashCode() : 0); + result = 31 * result + (vendorWebGljs != null ? vendorWebGljs.hashCode() : 0); + result = 31 * result + (rendererWebGljs != null ? rendererWebGljs.hashCode() : 0); + result = 31 * result + (octaneScore != null ? octaneScore.hashCode() : 0); + result = 31 * result + (sunspiderTime != null ? sunspiderTime.hashCode() : 0); + result = 31 * result + (pluginsJsHashed != null ? pluginsJsHashed.hashCode() : 0); + result = 31 * result + (canvasJsHashed != null ? canvasJsHashed.hashCode() : 0); + result = 31 * result + (webGlJsHashed != null ? webGlJsHashed.hashCode() : 0); + result = 31 * result + (fontsFlashHashed != null ? fontsFlashHashed.hashCode() : 0); + return result; + } + + public HashMap fpToHashMap(){ + HashMap fpHashMap = new HashMap(); + fpHashMap.put("id",this.getId()); + fpHashMap.put("creationDate", new SimpleDateFormat("yyyy,MM,dd,HH").format(this.getCreationDate())); + if(this.getUpdateDate() == null) { + fpHashMap.put("updateDate",""); + } else { + fpHashMap.put("updateDate", new SimpleDateFormat("yyyy,MM,dd,HH").format(this.getUpdateDate())); + } + + if(this.getEndDate() == null) { + fpHashMap.put("endDate", ""); + } else { + fpHashMap.put("endDate", new SimpleDateFormat("yyyy,MM,dd,HH").format(this.getEndDate())); + } + + fpHashMap.put("addressHttp",this.getAddressHttp()); + fpHashMap.put("userAgentHttp",this.getUserAgentHttp()); + fpHashMap.put("acceptHttp",this.getAcceptHttp()); + fpHashMap.put("hostHttp",this.getHostHttp()); + fpHashMap.put("connectionHttp",this.getConnectionHttp()); + fpHashMap.put("encodingHttp",this.getEncodingHttp()); + fpHashMap.put("languageHttp",this.getLanguageHttp()); + fpHashMap.put("orderHttp",this.getOrderHttp()); + fpHashMap.put("pluginsJs", this.getPluginsJs()); + fpHashMap.put("platformJs", this.getPlatformJs()); + fpHashMap.put("cookiesJs",this.getCookiesJs()); + fpHashMap.put("dntJs",this.getDntJs()); + fpHashMap.put("timezoneJs",this.getTimezoneJs()); + fpHashMap.put("resolutionJs",this.getResolutionJs()); + fpHashMap.put("localJs",this.getLocalJs()); + fpHashMap.put("sessionJs",this.getSessionJs()); + fpHashMap.put("ieDataJs",this.getIeDataJs()); + fpHashMap.put("canvasJs",this.getCanvasJs()); + fpHashMap.put("webGlJs",this.getWebGlJs()); + fpHashMap.put("fontsFlash",this.getFontsFlash()); + fpHashMap.put("resolutionFlash",this.getResolutionFlash()); + fpHashMap.put("languageFlash",this.getLanguageFlash()); + fpHashMap.put("platformFlash",this.getPlatformFlash()); + fpHashMap.put("adBlock",this.getAdBlock()); + fpHashMap.put("rendererWebGLJs",this.getRendererWebGljs()); + fpHashMap.put("vendorWebGLJs",this.getVendorWebGljs()); + fpHashMap.put("pluginsJsHashed",this.getPluginsJsHashed()); + fpHashMap.put("canvasJsHashed",this.getCanvasJsHashed()); + fpHashMap.put("webGLJsHashed",this.getWebGlJsHashed()); + fpHashMap.put("fontsFlashHashed",this.getFontsFlashHashed()); + + // New attributes + fpHashMap.put("hardwareConcurrency",this.getHardwareConcurrency()); + fpHashMap.put("availableScreenResolution",this.getAvailableScreenResolution()); + fpHashMap.put("cpuClass",this.getCpuClass()); + fpHashMap.put("modernizr",this.getModernizr()); + fpHashMap.put("overwrittenObjects",this.getOverwrittenObjects()); + fpHashMap.put("osMediaQueries",this.getOsMediaQueries()); + fpHashMap.put("appCodeName",this.getAppCodeName()); + fpHashMap.put("oscpu",this.getOscpu()); + fpHashMap.put("appName",this.getAppName()); + fpHashMap.put("appVersion",this.getAppVersion()); + fpHashMap.put("languages",this.getLanguages()); + fpHashMap.put("mimeTypes",this.getMimeTypes()); + fpHashMap.put("pluginsUsingMimeTypes",this.getPluginsUsingMimeTypes()); + fpHashMap.put("product",this.getProduct()); + fpHashMap.put("productSub",this.getProductSub()); + fpHashMap.put("vendor",this.getVendor()); + fpHashMap.put("vendorSub",this.getVendorSub()); + fpHashMap.put("touchSupport",this.getTouchSupport()); + fpHashMap.put("buildID",this.getBuildID()); + fpHashMap.put("navigatorPrototype",this.getNavigatorPrototype()); + fpHashMap.put("mathsConstants",this.getMathsConstants()); + fpHashMap.put("resOverflow",this.getResOverflow()); + fpHashMap.put("errorsGenerated",this.getErrorsGenerated()); + fpHashMap.put("unknownImageError",this.getUnknownImageError()); + fpHashMap.put("fontsEnum",this.getFontsEnum()); + fpHashMap.put("audio",this.getAudio()); + + return fpHashMap; + } + + public Object clone() { + Object o = null; + try { + o = super.clone(); + } catch(CloneNotSupportedException cnse) { + cnse.printStackTrace(System.err); + } + + return o; + } + + + public int compareTo(Object o){ + if (this == o) return 0; + if (o == null) return -1; + + ExtensionDataEntity that = (ExtensionDataEntity) o; + return that.getCounter() - getCounter(); + + } +} diff --git a/website/app/models/ExtensionDataEntityManager.java b/website/app/models/ExtensionDataEntityManager.java new file mode 100644 index 0000000..6f7a583 --- /dev/null +++ b/website/app/models/ExtensionDataEntityManager.java @@ -0,0 +1,198 @@ +package models; + +import play.db.jpa.JPA; +import javax.persistence.EntityManager; +import javax.persistence.Query; +import java.sql.Timestamp; +import java.util.List; +import java.util.TreeSet; +import java.util.function.Function; + +public class ExtensionDataEntityManager { + + private A withTransaction(Function f) { + try { + return JPA.withTransaction(() -> f.apply(JPA.em())); + } catch (Throwable throwable) { + throw new RuntimeException(throwable); + } + } + + public ExtensionDataEntity createFP(String id, String addressHttp, Timestamp creation, Timestamp update, Timestamp end, + String userAgentHttp,String acceptHttp, String hostHttp, String connectionHttp, String encodingHttp, + String languageHttp, String orderHttp, String pluginsJs, String platformJs, String cookiesJs, + String dntJs, String timezoneJs, String resolutionJs, String localJs, String sessionJs, + String ieDataJs, String canvasJs, String webGlJs, String fontsFlash, String resolutionFlash, + String languageFlash, String platformFlash, String adBlock, String vendorJs, + String rendererJs, String octaneScore, String sunspiderTime, String pluginsJsHashed, + String canvasJsHashed, String webGLJsHashed, String fontsFlashHashed, + String hardwareConcurrency, String availableScreenResolution, + String cpuClass, String modernizr, String overwrittenObjects, + String osMediaQueries, String appCodeName, String oscpu, + String appName, String appVersion, String languages, + String mimeTypes, String pluginsUsingMimeTypes, String product, + String productSub, String vendor, String vendorSub, String touchSupport, + String buildID, String navigatorPrototype, String mathsConstants, + String resOverflow, String errorsGenerated, + String unknownImageError, String fontsEnum, String audio) { + + return withTransaction(em -> { + ExtensionDataEntity fp = new ExtensionDataEntity(); + fp.setId(id); + fp.setAddressHttp(addressHttp); + fp.setCreationDate(creation); + fp.setUpdateDate(update); + fp.setEndDate(end); + fp.setUserAgentHttp(userAgentHttp); + fp.setAcceptHttp(acceptHttp); + fp.setHostHttp(hostHttp); + fp.setConnectionHttp(connectionHttp); + fp.setEncodingHttp(encodingHttp); + fp.setLanguageHttp(languageHttp); + fp.setOrderHttp(orderHttp); + fp.setPluginsJs(pluginsJs); + fp.setPlatformJs(platformJs); + fp.setCookiesJs(cookiesJs); + fp.setDntJs(dntJs); + fp.setTimezoneJs(timezoneJs); + fp.setResolutionJs(resolutionJs); + fp.setLocalJs(localJs); + fp.setSessionJs(sessionJs); + fp.setIeDataJs(ieDataJs); + fp.setCanvasJs(canvasJs); + fp.setWebGlJs(webGlJs); + fp.setFontsFlash(fontsFlash); + fp.setResolutionFlash(resolutionFlash); + fp.setLanguageFlash(languageFlash); + fp.setPlatformFlash(platformFlash); + fp.setAdBlock(adBlock); + fp.setVendorWebGljs(vendorJs); + fp.setRendererWebGljs(rendererJs); + fp.setOctaneScore(octaneScore); + fp.setSunspiderTime(sunspiderTime); + fp.setPluginsJsHashed(pluginsJsHashed); + fp.setCanvasJsHashed(canvasJsHashed); + fp.setWebGlJsHashed(webGLJsHashed); + fp.setFontsFlashHashed(fontsFlashHashed); + + // New attributes + fp.setHardwareConcurrency(hardwareConcurrency); + fp.setAvailableScreenResolution(availableScreenResolution); + fp.setCpuClass(cpuClass); + fp.setModernizr(modernizr); + fp.setOverwrittenObjects(overwrittenObjects); + fp.setOsMediaQueries(osMediaQueries); + fp.setAppCodeName(appCodeName); + fp.setOscpu(oscpu); + fp.setAppName(appName); + fp.setAppVersion(appVersion); + fp.setLanguages(languages); + fp.setMimeTypes(mimeTypes); + fp.setPluginsUsingMimeTypes(pluginsUsingMimeTypes); + fp.setProduct(product); + fp.setProductSub(productSub); + fp.setVendor(vendor); + fp.setVendorSub(vendorSub); + fp.setTouchSupport(touchSupport); + fp.setBuildID(buildID); + fp.setNavigatorPrototype(navigatorPrototype); + fp.setMathsConstants(mathsConstants); + fp.setResOverflow(resOverflow); + fp.setErrorsGenerated(errorsGenerated); + fp.setUnknownImageError(unknownImageError); + fp.setFontsEnum(fontsEnum); + fp.setAudio(audio); + + em.persist(fp); + return fp; + }); + } + + public boolean checkIfIDExists(String id){ + + String query = "SELECT count(*) FROM extensionData WHERE id= :id"; + int nbId = withTransaction(em -> { + Query q = em.createNativeQuery(query).setParameter("id", id); + return ((Number) q.getResultList().get(0)).intValue(); + }); + + return nbId > 0; + } + + public boolean checkIfLastFPIsIdentical(String id,String userAgentHttp, + String acceptHttp, String encodingHttp, + String languageHttp, String pluginsJsHashed, String platformJs, String cookiesJs, + String dntJs, String timezoneJs, String resolutionJs, String localJs, String sessionJs, + String ieDataJs, String canvasJsHashed, String fontsFlashHashed, String resolutionFlash, + String languageFlash, String platformFlash, String adBlock){ + + String query = "SELECT count(*) FROM ("+ + "SELECT * FROM extensionData WHERE id= :id ORDER BY counter DESC limit 1)b "+ + "WHERE acceptHttp= :acceptHttp AND userAgentHttp= :userAgentHttp AND encodingHttp= :encodingHttp "+ + "AND languageHttp= :languageHttp AND pluginsJSHashed = :pluginsJsHashed "+ + "AND platformJS= :platformJs AND cookiesJS= :cookiesJs AND dntJS= :dntJs " + + "AND timezoneJS= :timezoneJs AND resolutionJS= :resolutionJs AND localJS= :localJs "+ + "AND sessionJS= :sessionJs AND IEDataJS= :ieDataJs AND canvasJSHashed= :canvasJsHashed "+ + "AND fontsFlashHashed= :fontsFlashHashed AND resolutionFlash= :resolutionFlash "+ + "AND languageFlash= :languageFlash AND platformFlash= :platformFlash AND adBlock= :adBlock "; + + int nbId = withTransaction(em -> { + Query q = em.createNativeQuery(query) + .setParameter("id", id).setParameter("acceptHttp",acceptHttp).setParameter("userAgentHttp", userAgentHttp) + .setParameter("encodingHttp", encodingHttp); + q.setParameter("languageHttp",languageHttp).setParameter("pluginsJsHashed",pluginsJsHashed).setParameter("platformJs",platformJs) + .setParameter("cookiesJs", cookiesJs).setParameter("dntJs",dntJs).setParameter("timezoneJs",timezoneJs); + q.setParameter("resolutionJs",resolutionJs).setParameter("localJs",localJs).setParameter("sessionJs",sessionJs) + .setParameter("ieDataJs", ieDataJs).setParameter("canvasJsHashed",canvasJsHashed); + q.setParameter("fontsFlashHashed", fontsFlashHashed).setParameter("resolutionFlash",resolutionFlash) + .setParameter("languageFlash", languageFlash).setParameter("platformFlash",platformFlash) + .setParameter("adBlock", adBlock); + return ((Number) q.getResultList().get(0)).intValue(); + }); + return nbId == 1; + } + + + public int updateLastFP(String id, Timestamp currentTime){ + String query = "UPDATE extensionData SET updateDate= :updateDate WHERE id= :id ORDER BY counter DESC LIMIT 1"; + return withTransaction(em -> { + Query q = em.createNativeQuery(query).setParameter("updateDate",currentTime).setParameter("id", id); + return q.executeUpdate(); + }); + } + + public int endLastFP(String id, Timestamp currentTime){ + String query = "UPDATE extensionData SET endDate= :endDate WHERE id= :id ORDER BY counter DESC LIMIT 1"; + return withTransaction(em -> { + Query q = em.createNativeQuery(query).setParameter("endDate",currentTime).setParameter("id", id); + return q.executeUpdate(); + }); + } + + public ExtensionDataEntity getLastFP(String id){ + //return withTransaction(em -> em.find(ExtensionDataEntity.class,id)); + String query = "SELECT counter FROM extensionData WHERE id= :id ORDER BY counter DESC LIMIT 1"; + return withTransaction(em -> { + Query q = em.createNativeQuery(query).setParameter("id", id); + return em.find(ExtensionDataEntity.class,q.getResultList().get(0)); + //return (ExtensionDataEntity) q.getResultList().get(0); + }); + } + + public ExtensionDataEntity getExistingFPByCounter(int counter){ + return withTransaction(em -> em.find(ExtensionDataEntity.class,counter)); + } + + public TreeSet getExistingFPsById(String id){ + String query = "SELECT counter FROM extensionData WHERE id= :id"; + List counters= withTransaction(em -> (em.createNativeQuery(query).setParameter("id", id).getResultList())); + + TreeSet fps = new TreeSet(); + for(int c : counters){ + fps.add(getExistingFPByCounter(c)); + } + + return fps; + } + +} diff --git a/website/app/models/FpDataEntity.java b/website/app/models/FpDataEntity.java index bbcb712..2462163 100644 --- a/website/app/models/FpDataEntity.java +++ b/website/app/models/FpDataEntity.java @@ -1,17 +1,56 @@ package models; -import javax.persistence.Basic; -import javax.persistence.Entity; -import javax.persistence.Id; +import javax.persistence.*; import java.sql.Timestamp; +import java.util.HashMap; @Entity -@javax.persistence.Table(name = "fpData", schema = "", catalog = "fingerprint") -public class FpDataEntity { +@Table(name = "fpData", schema = "", catalog = "fingerprint") +public class FpDataEntity implements Comparable, Cloneable { private int counter; + private String id; + private String addressHttp; + private Timestamp time; + private String userAgentHttp; + private String acceptHttp; + private String hostHttp; + private String connectionHttp; + private String encodingHttp; + private String languageHttp; + private String orderHttp; + private String pluginsJs; + private String platformJs; + private String cookiesJs; + private String dntJs; + private String timezoneJs; + private String resolutionJs; + private String localJs; + private String sessionJs; + private String ieDataJs; + private String canvasJs; + private String fontsFlash; + private String resolutionFlash; + private String languageFlash; + private String platformFlash; + private String adBlock; + private String octaneScore; + private String sunspiderTime; + private String webGlJs; + private String vendorWebGljs; + private String rendererWebGljs; + + //Hashed Attributes + private String pluginsJsHashed; + private String canvasJsHashed; + private String webGLJsHashed; + private String fontsFlashHashed; + + public FpDataEntity() { + } @Id - @javax.persistence.Column(name = "counter") + @Column(name = "counter") + @GeneratedValue public int getCounter() { return counter; } @@ -20,10 +59,8 @@ public void setCounter(int counter) { this.counter = counter; } - private String id; - @Basic - @javax.persistence.Column(name = "id") + @Column(name = "id") public String getId() { return id; } @@ -32,11 +69,8 @@ public void setId(String id) { this.id = id; } - - private String addressHttp; - @Basic - @javax.persistence.Column(name = "addressHttp") + @Column(name = "addressHttp") public String getAddressHttp() { return addressHttp; } @@ -45,10 +79,8 @@ public void setAddressHttp(String addressHttp) { this.addressHttp = addressHttp; } - private Timestamp time; - @Basic - @javax.persistence.Column(name = "time") + @Column(name = "time") public Timestamp getTime() { return time; } @@ -57,10 +89,8 @@ public void setTime(Timestamp time) { this.time = time; } - private String userAgentHttp; - @Basic - @javax.persistence.Column(name = "userAgentHttp") + @Column(name = "userAgentHttp") public String getUserAgentHttp() { return userAgentHttp; } @@ -69,10 +99,8 @@ public void setUserAgentHttp(String userAgentHttp) { this.userAgentHttp = userAgentHttp; } - private String acceptHttp; - @Basic - @javax.persistence.Column(name = "acceptHttp") + @Column(name = "acceptHttp") public String getAcceptHttp() { return acceptHttp; } @@ -81,10 +109,8 @@ public void setAcceptHttp(String acceptHttp) { this.acceptHttp = acceptHttp; } - private String hostHttp; - @Basic - @javax.persistence.Column(name = "hostHttp") + @Column(name = "hostHttp") public String getHostHttp() { return hostHttp; } @@ -93,10 +119,8 @@ public void setHostHttp(String hostHttp) { this.hostHttp = hostHttp; } - private String connectionHttp; - @Basic - @javax.persistence.Column(name = "connectionHttp") + @Column(name = "connectionHttp") public String getConnectionHttp() { return connectionHttp; } @@ -105,10 +129,8 @@ public void setConnectionHttp(String connectionHttp) { this.connectionHttp = connectionHttp; } - private String encodingHttp; - @Basic - @javax.persistence.Column(name = "encodingHttp") + @Column(name = "encodingHttp") public String getEncodingHttp() { return encodingHttp; } @@ -117,10 +139,8 @@ public void setEncodingHttp(String encodingHttp) { this.encodingHttp = encodingHttp; } - private String languageHttp; - @Basic - @javax.persistence.Column(name = "languageHttp") + @Column(name = "languageHttp") public String getLanguageHttp() { return languageHttp; } @@ -129,10 +149,8 @@ public void setLanguageHttp(String languageHttp) { this.languageHttp = languageHttp; } - private String orderHttp; - @Basic - @javax.persistence.Column(name = "orderHttp") + @Column(name = "orderHttp") public String getOrderHttp() { return orderHttp; } @@ -141,10 +159,8 @@ public void setOrderHttp(String orderHttp) { this.orderHttp = orderHttp; } - private String pluginsJs; - @Basic - @javax.persistence.Column(name = "pluginsJS") + @Column(name = "pluginsJS") public String getPluginsJs() { return pluginsJs; } @@ -153,10 +169,8 @@ public void setPluginsJs(String pluginsJs) { this.pluginsJs = pluginsJs; } - private String platformJs; - @Basic - @javax.persistence.Column(name = "platformJS") + @Column(name = "platformJS") public String getPlatformJs() { return platformJs; } @@ -165,10 +179,8 @@ public void setPlatformJs(String platformJs) { this.platformJs = platformJs; } - private String cookiesJs; - @Basic - @javax.persistence.Column(name = "cookiesJS") + @Column(name = "cookiesJS") public String getCookiesJs() { return cookiesJs; } @@ -177,10 +189,8 @@ public void setCookiesJs(String cookiesJs) { this.cookiesJs = cookiesJs; } - private String dntJs; - @Basic - @javax.persistence.Column(name = "dntJS") + @Column(name = "dntJS") public String getDntJs() { return dntJs; } @@ -189,10 +199,8 @@ public void setDntJs(String dntJs) { this.dntJs = dntJs; } - private String timezoneJs; - @Basic - @javax.persistence.Column(name = "timezoneJS") + @Column(name = "timezoneJS") public String getTimezoneJs() { return timezoneJs; } @@ -201,10 +209,8 @@ public void setTimezoneJs(String timezoneJs) { this.timezoneJs = timezoneJs; } - private String resolutionJs; - @Basic - @javax.persistence.Column(name = "resolutionJS") + @Column(name = "resolutionJS") public String getResolutionJs() { return resolutionJs; } @@ -213,10 +219,8 @@ public void setResolutionJs(String resolutionJs) { this.resolutionJs = resolutionJs; } - private String localJs; - @Basic - @javax.persistence.Column(name = "localJS") + @Column(name = "localJS") public String getLocalJs() { return localJs; } @@ -225,10 +229,8 @@ public void setLocalJs(String localJs) { this.localJs = localJs; } - private String sessionJs; - @Basic - @javax.persistence.Column(name = "sessionJS") + @Column(name = "sessionJS") public String getSessionJs() { return sessionJs; } @@ -237,10 +239,8 @@ public void setSessionJs(String sessionJs) { this.sessionJs = sessionJs; } - private String ieDataJs; - @Basic - @javax.persistence.Column(name = "IEDataJS") + @Column(name = "IEDataJS") public String getIeDataJs() { return ieDataJs; } @@ -249,10 +249,8 @@ public void setIeDataJs(String ieDataJs) { this.ieDataJs = ieDataJs; } - private String canvasJs; - @Basic - @javax.persistence.Column(name = "canvasJS") + @Column(name = "canvasJS") public String getCanvasJs() { return canvasJs; } @@ -261,22 +259,8 @@ public void setCanvasJs(String canvasJs) { this.canvasJs = canvasJs; } - private String webGljs; - - @Basic - @javax.persistence.Column(name = "webGLJS") - public String getWebGljs() { - return webGljs; - } - - public void setWebGljs(String webGljs) { - this.webGljs = webGljs; - } - - private String fontsFlash; - @Basic - @javax.persistence.Column(name = "fontsFlash") + @Column(name = "fontsFlash") public String getFontsFlash() { return fontsFlash; } @@ -285,10 +269,8 @@ public void setFontsFlash(String fontsFlash) { this.fontsFlash = fontsFlash; } - private String resolutionFlash; - @Basic - @javax.persistence.Column(name = "resolutionFlash") + @Column(name = "resolutionFlash") public String getResolutionFlash() { return resolutionFlash; } @@ -297,10 +279,8 @@ public void setResolutionFlash(String resolutionFlash) { this.resolutionFlash = resolutionFlash; } - private String languageFlash; - @Basic - @javax.persistence.Column(name = "languageFlash") + @Column(name = "languageFlash") public String getLanguageFlash() { return languageFlash; } @@ -309,10 +289,8 @@ public void setLanguageFlash(String languageFlash) { this.languageFlash = languageFlash; } - private String platformFlash; - @Basic - @javax.persistence.Column(name = "platformFlash") + @Column(name = "platformFlash") public String getPlatformFlash() { return platformFlash; } @@ -321,10 +299,8 @@ public void setPlatformFlash(String platformFlash) { this.platformFlash = platformFlash; } - private String adBlock; - @Basic - @javax.persistence.Column(name = "adBlock") + @Column(name = "adBlock") public String getAdBlock() { return adBlock; } @@ -333,10 +309,8 @@ public void setAdBlock(String adBlock) { this.adBlock = adBlock; } - private String octaneScore; - @Basic - @javax.persistence.Column(name = "octaneScore") + @Column(name = "octaneScore") public String getOctaneScore() { return octaneScore; } @@ -345,10 +319,8 @@ public void setOctaneScore(String octaneScore) { this.octaneScore = octaneScore; } - private String sunspiderTime; - @Basic - @javax.persistence.Column(name = "sunspiderTime") + @Column(name = "sunspiderTime") public String getSunspiderTime() { return sunspiderTime; } @@ -357,10 +329,54 @@ public void setSunspiderTime(String sunspiderTime) { this.sunspiderTime = sunspiderTime; } + + @Basic + @Column(name = "pluginsJSHashed") + public String getPluginsJsHashed() { + return pluginsJsHashed; + } + + public void setPluginsJsHashed(String pluginsJsHashed){ + this.pluginsJsHashed = pluginsJsHashed; + } + + + @Basic + @Column(name = "canvasJSHashed") + public String getCanvasJsHashed() { + return canvasJsHashed; + } + + public void setCanvasJsHashed(String canvasJsHashed){ + this.canvasJsHashed = canvasJsHashed; + } + + + @Basic + @Column(name = "webGLJsHashed") + public String getWebGLJsHashed() { + return webGLJsHashed; + } + + public void setWebGLJsHashed(String webGLJsHashed){ + this.webGLJsHashed = webGLJsHashed; + } + + + @Basic + @Column(name = "fontsFlashHashed") + public String getFontsFlashHashed() { + return fontsFlashHashed; + } + + public void setFontsFlashHashed(String fontsFlashHashed){ + this.fontsFlashHashed = fontsFlashHashed; + } + @Override public boolean equals(Object o) { if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if (o == null) return false; FpDataEntity that = (FpDataEntity) o; @@ -398,7 +414,12 @@ public boolean equals(Object o) { if (timezoneJs != null ? !timezoneJs.equals(that.timezoneJs) : that.timezoneJs != null) return false; if (userAgentHttp != null ? !userAgentHttp.equals(that.userAgentHttp) : that.userAgentHttp != null) return false; - if (webGljs != null ? !webGljs.equals(that.webGljs) : that.webGljs != null) return false; + if (webGlJs != null ? !webGlJs.equals(that.webGlJs) : that.webGlJs != null) return false; + + if (pluginsJsHashed != null ? !pluginsJsHashed.equals(that.pluginsJsHashed) : that.pluginsJsHashed != null) return false; + if (canvasJsHashed != null ? !canvasJsHashed.equals(that.canvasJsHashed) : that.canvasJsHashed != null) return false; + if (webGLJsHashed != null ? !webGLJsHashed.equals(that.webGLJsHashed) : that.webGLJsHashed != null) return false; + if (fontsFlashHashed!= null ? !fontsFlashHashed.equals(that.fontsFlashHashed) : that.fontsFlashHashed != null) return false; return true; } @@ -426,7 +447,7 @@ public int hashCode() { result = 31 * result + (sessionJs != null ? sessionJs.hashCode() : 0); result = 31 * result + (ieDataJs != null ? ieDataJs.hashCode() : 0); result = 31 * result + (canvasJs != null ? canvasJs.hashCode() : 0); - result = 31 * result + (webGljs != null ? webGljs.hashCode() : 0); + result = 31 * result + (webGlJs != null ? webGlJs.hashCode() : 0); result = 31 * result + (fontsFlash != null ? fontsFlash.hashCode() : 0); result = 31 * result + (resolutionFlash != null ? resolutionFlash.hashCode() : 0); result = 31 * result + (languageFlash != null ? languageFlash.hashCode() : 0); @@ -434,9 +455,101 @@ public int hashCode() { result = 31 * result + (adBlock != null ? adBlock.hashCode() : 0); result = 31 * result + (octaneScore != null ? octaneScore.hashCode() : 0); result = 31 * result + (sunspiderTime != null ? sunspiderTime.hashCode() : 0); + + result = 31 * result + (pluginsJsHashed != null ? pluginsJsHashed.hashCode() : 0); + result = 31 * result + (canvasJsHashed != null ? canvasJsHashed.hashCode() : 0); + result = 31 * result + (webGLJsHashed != null ? webGLJsHashed.hashCode() : 0); + result = 31 * result + (fontsFlashHashed != null ? fontsFlashHashed.hashCode() : 0); + return result; } - public FpDataEntity() { + public HashMap fpToHashMap(){ + HashMap fpHashMap = new HashMap(); + + fpHashMap.put("id",this.getId()); + fpHashMap.put("addressHttp",this.getAddressHttp()); + fpHashMap.put("userAgentHttp",this.getUserAgentHttp()); + fpHashMap.put("acceptHttp",this.getAcceptHttp()); + fpHashMap.put("hostHttp",this.getHostHttp()); + fpHashMap.put("connectionHttp",this.getConnectionHttp()); + fpHashMap.put("encodingHttp",this.getEncodingHttp()); + fpHashMap.put("languageHttp",this.getLanguageHttp()); + fpHashMap.put("orderHttp",this.getOrderHttp()); + fpHashMap.put("pluginsJs",this.getPluginsJs()); + fpHashMap.put("platformJs",this.getPlatformJs()); + fpHashMap.put("cookiesJs",this.getCookiesJs()); + fpHashMap.put("dntJs",this.getDntJs()); + fpHashMap.put("timezoneJs",this.getTimezoneJs()); + fpHashMap.put("resolutionJs",this.getResolutionJs()); + fpHashMap.put("localJs",this.getLocalJs()); + fpHashMap.put("sessionJs",this.getSessionJs()); + fpHashMap.put("ieDataJs",this.getIeDataJs()); + fpHashMap.put("canvasJs",this.getCanvasJs()); + fpHashMap.put("webGlJs",this.getWebGlJs()); + fpHashMap.put("fontsFlash",this.getFontsFlash()); + fpHashMap.put("resolutionFlash",this.getResolutionFlash()); + fpHashMap.put("languageFlash",this.getLanguageFlash()); + fpHashMap.put("platformFlash",this.getPlatformFlash()); + fpHashMap.put("adBlock",this.getAdBlock()); + fpHashMap.put("octaneScore",this.getOctaneScore()); + fpHashMap.put("sunspiderTime",this.getSunspiderTime()); + + fpHashMap.put("pluginsJsHashed",this.getPluginsJsHashed()); + fpHashMap.put("canvasJsHashed",this.getCanvasJsHashed()); + fpHashMap.put("webGLJsHashed",this.getWebGLJsHashed()); + fpHashMap.put("fontsFlashHashed",this.getFontsFlashHashed()); + + return fpHashMap; + } + + @Basic + @Column(name = "webGLJs") + public String getWebGlJs() { + return webGlJs; + } + + public void setWebGlJs(String webGlJs) { + this.webGlJs = webGlJs; + } + + @Basic + @Column(name = "vendorWebGLJS") + public String getVendorWebGljs() { + return vendorWebGljs; + } + + public void setVendorWebGljs(String vendorWebGljs) { + this.vendorWebGljs = vendorWebGljs; + } + + @Basic + @Column(name = "rendererWebGLJS") + public String getRendererWebGljs() { + return rendererWebGljs; + } + + public void setRendererWebGljs(String rendererWebGljs) { + this.rendererWebGljs = rendererWebGljs; + } + + public int compareTo(Object o){ + if (this == o) return 0; + if (o == null) return -1; + + FpDataEntity that = (FpDataEntity) o; + return that.getCounter() - getCounter(); + + } + + public Object clone() { + Object o = null; + try { + o = super.clone(); + } catch(CloneNotSupportedException cnse) { + cnse.printStackTrace(System.err); + } + + return o; } } diff --git a/website/app/models/FpDataEntityManager.java b/website/app/models/FpDataEntityManager.java index 1b0b813..065e0a0 100644 --- a/website/app/models/FpDataEntityManager.java +++ b/website/app/models/FpDataEntityManager.java @@ -1,9 +1,14 @@ package models; import com.fasterxml.jackson.databind.JsonNode; +import org.hibernate.ScrollMode; +import org.hibernate.ScrollableResults; +import org.hibernate.Session; +import org.hibernate.StatelessSession; import play.db.jpa.JPA; + import javax.persistence.EntityManager; -import java.math.BigInteger; +import javax.persistence.Query; import java.sql.Timestamp; import java.util.*; import java.util.function.Function; @@ -21,49 +26,85 @@ private A withTransaction(Function f) { } public boolean checkIfFPExists(String id,String userAgentHttp, - String acceptHttp, String connectionHttp, String encodingHttp, - String languageHttp, String pluginsJs, String platformJs, String cookiesJs, + String acceptHttp, String encodingHttp, + String languageHttp, String pluginsJsHashed, String platformJs, String cookiesJs, String dntJs, String timezoneJs, String resolutionJs, String localJs, String sessionJs, - String ieDataJs, String canvasJs, String webGljs, String fontsFlash, String resolutionFlash, + String ieDataJs, String canvasJsHashed, String webGlJsHashed, String fontsFlashHashed, String resolutionFlash, String languageFlash, String platformFlash, String adBlock){ - String query = "SELECT count(*) FROM FpDataEntity WHERE id= \'"+id+"\' AND acceptHttp=\'"+acceptHttp+ - "\' AND userAgentHttp= \'"+userAgentHttp+"\' AND connectionHttp=\'"+connectionHttp+ - "\' AND encodingHttp=\'"+encodingHttp+"\' AND languageHttp=\'"+languageHttp+"\' AND pluginsJS =\'"+ - pluginsJs+"\' AND platformJS=\'"+platformJs+"\' AND cookiesJs=\'"+cookiesJs+"\' AND dntJs=\'"+dntJs+ - "\' AND timezoneJs=\'"+timezoneJs+"\' AND resolutionJs=\'"+resolutionJs+"\' AND localJs=\'"+ - localJs+"\' AND sessionJs=\'"+sessionJs+"\' AND ieDataJs=\'"+ieDataJs+"\' AND canvasJs=\'"+ - canvasJs+"\' AND webGljs=\'"+webGljs+"\' AND fontsFlash=\'"+fontsFlash+"\' AND resolutionFlash=\'"+ - resolutionFlash+"\' AND languageFlash=\'"+languageFlash+"\' AND platformFlash=\'"+ - platformFlash+"\' AND adBlock=\'"+adBlock+"\'"; - int nbId = withTransaction(em -> ((Long) em.createQuery(query).getResultList().get(0)).intValue()); + + String query = "SELECT count(*) FROM FpDataEntity WHERE id= :id AND acceptHttp= :acceptHttp " + + "AND userAgentHttp= :userAgentHttp AND encodingHttp= :encodingHttp "+ + "AND languageHttp= :languageHttp AND pluginsJsHashed = :pluginsJsHashed "+ + "AND platformJs= :platformJs AND cookiesJs= :cookiesJs AND dntJs= :dntJs " + + "AND timezoneJs= :timezoneJs AND resolutionJs= :resolutionJs AND localJs= :localJs "+ + "AND sessionJs= :sessionJs AND ieDataJs= :ieDataJs AND canvasJsHashed= :canvasJsHashed "+ + "AND webGlJsHashed= :webGlJsHashed AND fontsFlashHashed= :fontsFlashHashed AND resolutionFlash= :resolutionFlash "+ + "AND languageFlash= :languageFlash AND platformFlash= :platformFlash AND adBlock= :adBlock "; + + int nbId = withTransaction(em -> { + Query q = em.createQuery(query) + .setParameter("id", id).setParameter("acceptHttp",acceptHttp).setParameter("userAgentHttp", userAgentHttp) + .setParameter("encodingHttp", encodingHttp); + q.setParameter("languageHttp",languageHttp).setParameter("pluginsJsHashed",pluginsJsHashed).setParameter("platformJs",platformJs) + .setParameter("cookiesJs", cookiesJs).setParameter("dntJs",dntJs).setParameter("timezoneJs",timezoneJs); + q.setParameter("resolutionJs",resolutionJs).setParameter("localJs",localJs).setParameter("sessionJs",sessionJs) + .setParameter("ieDataJs", ieDataJs).setParameter("canvasJsHashed",canvasJsHashed).setParameter("webGlJsHashed",webGlJsHashed); + q.setParameter("fontsFlashHashed", fontsFlashHashed).setParameter("resolutionFlash",resolutionFlash) + .setParameter("languageFlash", languageFlash).setParameter("platformFlash",platformFlash) + .setParameter("adBlock", adBlock); + return ((Long) q.getResultList().get(0)).intValue(); + }); return nbId == 1; } public boolean checkIfFPWithNoJsExists(String id,String userAgentHttp, - String acceptHttp, String connectionHttp, String encodingHttp, + String acceptHttp, String encodingHttp, String languageHttp){ - String query = "SELECT count(*) FROM FpDataEntity WHERE id= \'"+id+"\' AND userAgentHttp=\'"+userAgentHttp+ - "\' AND acceptHttp= \'"+acceptHttp+"\' AND connectionHttp=\'"+connectionHttp+ - "\' AND encodingHttp=\'"+encodingHttp+"\' AND languageHttp=\'"+languageHttp+"\' AND pluginsJS =\'no JS\'"; - int nbId = withTransaction(em -> ((Long) em.createQuery(query).getResultList().get(0)).intValue()); + + String query = "SELECT count(*) FROM FpDataEntity WHERE id= :id AND acceptHttp= :acceptHttp " + + "AND userAgentHttp= :userAgentHttp AND encodingHttp= :encodingHttp "+ + "AND languageHttp= :languageHttp AND pluginsJS =\'no JS\'"; + + int nbId = withTransaction(em -> ((Long) em.createQuery(query) + .setParameter("id", id).setParameter("acceptHttp",acceptHttp).setParameter("userAgentHttp", userAgentHttp) + .setParameter("encodingHttp", encodingHttp).setParameter("languageHttp",languageHttp) + .getResultList().get(0)).intValue()); return nbId == 1; } - public FpDataEntity getExistingFP(String id){ - String query = "SELECT counter FROM FpDataEntity WHERE id= \'"+id+"\'"; - int counter = withTransaction(em -> ((Integer) em.createQuery(query).getResultList().get(0)).intValue()); + public FpDataEntity getExistingFPById(String id){ + String query = "SELECT counter FROM FpDataEntity WHERE id= :id"; + int counter = withTransaction(em -> ((Integer) em.createQuery(query).setParameter("id", id).getResultList().get(0)).intValue()); + return withTransaction(em -> em.find(FpDataEntity.class,counter)); + } + + + public FpDataEntity getExistingFPByCounter(int counter){ return withTransaction(em -> em.find(FpDataEntity.class,counter)); } + public TreeSet getExistingFPsById(String id){ + String query = "SELECT counter FROM FpDataEntity WHERE id= :id"; + List counters= withTransaction(em -> (em.createQuery(query).setParameter("id", id).getResultList())); + + TreeSet fps = new TreeSet(); + for(int c : counters){ + fps.add(getExistingFPByCounter(c)); + } + + return fps; + } public FpDataEntity createFull(String id, String addressHttp, Timestamp time, String userAgentHttp, String acceptHttp, String hostHttp, String connectionHttp, String encodingHttp, String languageHttp, String orderHttp, String pluginsJs, String platformJs, String cookiesJs, String dntJs, String timezoneJs, String resolutionJs, String localJs, String sessionJs, - String ieDataJs, String canvasJs, String webGljs, String fontsFlash, String resolutionFlash, - String languageFlash, String platformFlash, String adBlock, String octaneScore, String sunspiderTime) { + String ieDataJs, String canvasJs, String webGlJs, String fontsFlash, String resolutionFlash, + String languageFlash, String platformFlash, String adBlock, String vendorJs, + String rendererJs, String octaneScore, String sunspiderTime, String pluginsJsHashed, + String canvasJsHashed, String webGLJsHashed, String fontsFlashHashed) { return withTransaction(em -> { FpDataEntity fp = new FpDataEntity(); fp.setId(id); @@ -86,14 +127,20 @@ public FpDataEntity createFull(String id, String addressHttp, Timestamp time, St fp.setSessionJs(sessionJs); fp.setIeDataJs(ieDataJs); fp.setCanvasJs(canvasJs); - fp.setWebGljs(webGljs); + fp.setWebGlJs(webGlJs); fp.setFontsFlash(fontsFlash); fp.setResolutionFlash(resolutionFlash); fp.setLanguageFlash(languageFlash); fp.setPlatformFlash(platformFlash); fp.setAdBlock(adBlock); + fp.setVendorWebGljs(vendorJs); + fp.setRendererWebGljs(rendererJs); fp.setOctaneScore(octaneScore); fp.setSunspiderTime(sunspiderTime); + fp.setPluginsJsHashed(pluginsJsHashed); + fp.setCanvasJsHashed(canvasJsHashed); + fp.setWebGLJsHashed(webGLJsHashed); + fp.setFontsFlashHashed(fontsFlashHashed); em.persist(fp); return fp; }); @@ -103,14 +150,16 @@ public FpDataEntity createWithoutFlash(String id, String addressHttp, Timestamp String acceptHttp, String hostHttp, String connectionHttp, String encodingHttp, String languageHttp, String orderHttp, String pluginsJs, String platformJs, String cookiesJs, String dntJs, String timezoneJs, String resolutionJs, String localJs, String sessionJs, - String ieDataJs, String canvasJs, String webGljs, String adBlock, - String octaneScore, String sunspiderTime) { + String ieDataJs, String canvasJs, String webGlJs, String adBlock, String vendorJs, String rendererJs, + String octaneScore, String sunspiderTime, String pluginsJsHashed, + String canvasJsHashed, String webGLJsHashed) { return createFull(id, addressHttp, time, userAgentHttp, acceptHttp, hostHttp, connectionHttp, encodingHttp, languageHttp, orderHttp, pluginsJs, platformJs, cookiesJs, dntJs, timezoneJs, resolutionJs, localJs, sessionJs, - ieDataJs, canvasJs, webGljs, "", "", - "", "", adBlock, octaneScore, sunspiderTime); + ieDataJs, canvasJs, webGlJs, "", "", + "", "", adBlock, vendorJs, rendererJs, octaneScore, sunspiderTime, + pluginsJsHashed, canvasJsHashed, webGLJsHashed, ""); } @@ -124,7 +173,8 @@ public FpDataEntity createWithoutJavaScript(String id, String addressHttp, Times languageHttp, orderHttp, noJs, noJs, noJs, noJs, noJs, noJs, noJs, noJs, noJs, noJs, noJs, noJs, noJs, - noJs, noJs, noJs, noJs, noJs); + noJs, noJs, noJs, noJs, noJs, + noJs, noJs, noJs, noJs, noJs, noJs); } @@ -133,7 +183,6 @@ public Map getPercentages(JsonNode values) { //Get the total number of entries in the database String nbTotalQuery = "SELECT count(*) FROM FpDataEntity"; double nbTotal = withTransaction(em -> ((Long) em.createQuery(nbTotalQuery).getResultList().get(0)).doubleValue()); - /** For each attribute -> computation of the percentage of each value @@ -147,51 +196,44 @@ public Map getPercentages(JsonNode values) { String column = it.next(); //Computation of the percentage - String nbSameValueQuery = (nbSameValueBaseQuery+column+" = "+values.get(column)).replace("\"","'"); - double nbSameValue = withTransaction(em -> ((Long) em.createQuery(nbSameValueQuery).getResultList().get(0)).doubleValue()); + String nbSameValueQuery = nbSameValueBaseQuery+column+" = :"+column; + double nbSameValue = withTransaction(em -> ((Long) em.createQuery(nbSameValueQuery) + .setParameter(column, (values.get(column).asText()).replace("\"", "'")) + .getResultList().get(0)).doubleValue()); if(nbSameValue != 1.0) { percentage.put(column, nbSameValue * 100 / nbTotal); } else { percentage.put(column, -1.0); } } - percentage.put("nbTotal",nbTotal); return percentage; } - public Map getPlatformStats(){ - ArrayList platform = new ArrayList<>(); - platform.add("Mac"); platform.add("Win"); platform.add("Linux"); - Map platformMap = new HashMap(platform.size()); - Long nb = (long) 0; - for(int i =0; i ((Long) em.createQuery(query).getResultList().get(0))); - nb += nbPlatform; - platformMap.put(platform.get(i),nbPlatform); - } - - String nbTotalQuery = "SELECT count(*) FROM FpDataEntity"; - Long nbTotal = withTransaction(em -> ((Long) em.createQuery(nbTotalQuery).getResultList().get(0))); - platformMap.put("Others",nbTotal-nb); - return platformMap; - } - public int getNumberOfIdenticalFingerprints(JsonNode values){ String query = "SELECT count(*) FROM FpDataEntity WHERE "; Iterator it = values.fieldNames(); String column = it.next(); - query+=column+" = "+values.get(column); + query+=column+" = :"+column; + //Building query while(it.hasNext()) { column = it.next(); - query+=" AND "+column+" = \""+values.get(column.toString())+"\""; + query+=" AND "+column+" = :"+column; } - - query = query.replace("\"\"","'").replace("\"","'"); String finalQuery = query; - return withTransaction(em -> ((Long) em.createQuery(finalQuery).getResultList().get(0)).intValue()); + + return withTransaction(em -> { + Iterator itQ = values.fieldNames(); + String col = itQ.next(); + Query q = em.createQuery(finalQuery); + q.setParameter(col,(values.get(col).asText()).replace("\"", "'")); + while(itQ.hasNext()) { + col = itQ.next(); + q.setParameter(col,(values.get(col).asText()).replace("\"", "'")); + } + return ((Long) q.getResultList().get(0)).intValue(); + }); } public int getNumberOfEntries(){ @@ -199,31 +241,80 @@ public int getNumberOfEntries(){ return withTransaction(em -> ((Long) em.createQuery(nbTotalQuery).getResultList().get(0)).intValue()); } - public int getNumberOfUniqueEntries(){ - String uniqueQuery = "SELECT COUNT(*) FROM (" + - "SELECT COUNT(*) as num FROM fpData GROUP BY userAgentHttp, acceptHttp, connectionHttp, " + - "encodingHttp, languageHttp, orderHttp, pluginsJS, platformJS, cookiesJS, dntJS, timezoneJS, " + - "resolutionJS, localJS, sessionJS, IEDataJS, canvasJS, webGLJS, fontsFlash, resolutionFlash, " + - "languageFlash, platformFlash, adBlock" + - ") as T " + - "where num = 1"; - return withTransaction(em -> ((BigInteger) em.createNativeQuery(uniqueQuery).getResultList().get(0)).intValue()); + public int getNumberOfEntriesSinceDate(Timestamp tsl, Timestamp tsu){ + String nbTotalQuery = "SELECT count(*) FROM FpDataEntity WHERE time BETWEEN :lower AND :upper"; + return withTransaction(em -> ((Long) em.createQuery(nbTotalQuery) + .setParameter("lower",tsl) + .setParameter("upper",tsu) + .getResultList().get(0)).intValue()); } - public ArrayList getTimezoneStats(){ + public CounterMap getTimezoneStats(){ String timeQuery = "SELECT timezoneJS, count(timezoneJS) FROM fpData GROUP BY timezoneJS"; - ArrayList res = withTransaction(em -> (ArrayList) (em.createNativeQuery(timeQuery).getResultList())); + ArrayList q = withTransaction(em -> (ArrayList) (em.createNativeQuery(timeQuery).getResultList())); + + CounterMap res = new CounterMap(); + for(Object[] obj: q){ + res.add(obj[0].toString(), obj[1].toString()); + } return res; } - public Vermap getLanguageStats(){ + public CounterMap getTimezoneStatsSinceDate(Timestamp tsl, Timestamp tsu){ + String timeQuery = "SELECT timezoneJS, count(timezoneJS) FROM fpData WHERE time BETWEEN :lower AND :upper GROUP BY timezoneJS"; + ArrayList q = withTransaction(em -> (ArrayList) (em.createNativeQuery(timeQuery) + .setParameter("lower",tsl) + .setParameter("upper",tsu) + .getResultList())); + + CounterMap res = new CounterMap(); + for(Object[] obj: q){ + res.add(obj[0].toString(), obj[1].toString()); + } + return res; + } + + + public VersionMap getLanguageStats(){ String query = "SELECT languageHttp FROM FpDataEntity"; - ArrayList langList = withTransaction(em -> ((ArrayList) em.createQuery(query).getResultList())); - Pattern langP = Pattern.compile("^(\\S\\S)"); - Vermap langMap = new Vermap(); + return withTransaction(em -> { + Session session = (Session) em.getDelegate(); + StatelessSession stateless = session.getSessionFactory().openStatelessSession(); + stateless.beginTransaction(); + ScrollableResults langList = stateless.createQuery(query).scroll(ScrollMode.FORWARD_ONLY); - for(int i=0; i { + Session session = (Session) em.getDelegate(); + StatelessSession stateless = session.getSessionFactory().openStatelessSession(); + stateless.beginTransaction(); + ScrollableResults langList = stateless.createQuery(query) + .setParameter("lower",tsl) + .setParameter("upper",tsu) + .scroll(ScrollMode.FORWARD_ONLY); + + VersionMap res = parseLanguages(langList); + + stateless.getTransaction().commit(); + stateless.close(); + return res; + }); + } + + public VersionMap parseLanguages(ScrollableResults langList){ + Pattern langP = Pattern.compile("^(\\S\\S)"); + VersionMap langMap = new VersionMap(); + while (langList.next()) { + Matcher langM = langP.matcher((String) langList.get(0)); if(langM.find()) { langMap.add(langM.group(1)); } else { @@ -233,52 +324,120 @@ public Vermap getLanguageStats(){ return langMap; } - public HashMap> getOSBrowserStats(){ + public HashMap> getOSBrowserStats(){ String query = "SELECT userAgentHttp FROM FpDataEntity"; - ArrayList userAgents = withTransaction(em -> ((ArrayList) em.createQuery(query).getResultList())); + return withTransaction(em -> { + Session session = (Session) em.getDelegate(); + StatelessSession stateless = session.getSessionFactory().openStatelessSession(); + stateless.beginTransaction(); + ScrollableResults userAgentList = stateless.createQuery(query).scroll(ScrollMode.FORWARD_ONLY); + + HashMap> res = parseUserAgents(userAgentList); + + stateless.getTransaction().commit(); + stateless.close(); + return res; + }); + } + + public HashMap> getOSBrowserStatsSinceDate(Timestamp tsl, Timestamp tsu){ + String query = "SELECT userAgentHttp FROM FpDataEntity WHERE time BETWEEN :lower AND :upper"; + return withTransaction(em -> { + Session session = (Session) em.getDelegate(); + StatelessSession stateless = session.getSessionFactory().openStatelessSession(); + stateless.beginTransaction(); + ScrollableResults userAgentList = stateless.createQuery(query) + .setParameter("lower",tsl) + .setParameter("upper",tsu) + .scroll(ScrollMode.FORWARD_ONLY); + + HashMap> res = parseUserAgents(userAgentList); + + stateless.getTransaction().commit(); + stateless.close(); + return res; + }); + } + + public HashMap> parseUserAgents(ScrollableResults userAgents){ /* Browser */ - HashMap browsers = new HashMap<>(); - browsers.put("Firefox", new Vermap()); - browsers.put("Chrome", new Vermap()); - browsers.put("Safari", new Vermap()); - browsers.put("IE", new Vermap()); - browsers.put("Opera", new Vermap()); - browsers.put("Others", new Vermap()); + HashMap browsers = new HashMap<>(); + browsers.put("Firefox", new VersionMap()); + browsers.put("Chrome", new VersionMap()); + browsers.put("Safari", new VersionMap()); + browsers.put("IE", new VersionMap()); + browsers.put("Edge", new VersionMap()); + browsers.put("Opera", new VersionMap()); + browsers.put("Others", new VersionMap()); /* OS */ - HashMap os = new HashMap<>(); - os.put("Windows", new Vermap()); - os.put("Linux", new Vermap()); - os.put("Mac", new Vermap()); - os.put("Android", new Vermap()); - os.put("iOS", new Vermap()); - os.put("Windows Phone", new Vermap()); - os.put("Others", new Vermap()); + HashMap os = new HashMap<>(); + os.put("Windows", new VersionMap()); + os.put("Linux", new VersionMap()); + os.put("Mac", new VersionMap()); + os.put("Android", new VersionMap()); + os.put("iOS", new VersionMap()); + os.put("Windows Phone", new VersionMap()); + os.put("Others", new VersionMap()); /* Parse user agents */ - for(int i=0; i> res = new HashMap<>(); + HashMap> res = new HashMap<>(); res.put("browsers",browsers); res.put("os",os); return res; } - public Rangemap getFontsStats(){ + public RangeMap getFontsStats(){ String query = "SELECT fontsFlash FROM FpDataEntity"; - ArrayList fontsList = withTransaction(em -> ((ArrayList) em.createQuery(query).getResultList())); - Rangemap nbFontsMap = new Rangemap(); + return withTransaction(em -> { + Session session = (Session) em.getDelegate(); + StatelessSession stateless = session.getSessionFactory().openStatelessSession(); + stateless.beginTransaction(); + ScrollableResults fontsList = stateless.createQuery(query).scroll(ScrollMode.FORWARD_ONLY); + + RangeMap nbFontsMap = parseFonts(fontsList); - for(int i=0; i2) { + stateless.getTransaction().commit(); + stateless.close(); + return nbFontsMap; + }); + } + + public RangeMap getFontsStatsSinceDate(Timestamp tsl, Timestamp tsu){ + String query = "SELECT fontsFlash FROM FpDataEntity WHERE time BETWEEN :lower AND :upper"; + return withTransaction(em -> { + Session session = (Session) em.getDelegate(); + StatelessSession stateless = session.getSessionFactory().openStatelessSession(); + stateless.beginTransaction(); + ScrollableResults fontsList = stateless.createQuery(query) + .setParameter("lower",tsl) + .setParameter("upper",tsu) + .scroll(ScrollMode.FORWARD_ONLY); + + RangeMap nbFontsMap = parseFonts(fontsList); + + stateless.getTransaction().commit(); + stateless.close(); + return nbFontsMap; + }); + } + + public RangeMap parseFonts(ScrollableResults fontsList){ + RangeMap nbFontsMap = new RangeMap(); + + while (fontsList.next()) { + String fonts = (String) fontsList.get(0); + Integer nbFonts = fonts.split("_").length; + if (nbFonts > 2) { int step = 50; int j = step; while (j < nbFonts) { @@ -291,6 +450,20 @@ public Rangemap getFontsStats(){ } + public ArrayList getAttribute(String attribute){ + String query = "SELECT "+attribute+" FROM FpDataEntity"; + ArrayList listAttribute = withTransaction(em -> ((ArrayList) em.createQuery(query).getResultList())); + + return listAttribute; + } + + public List getStatsAttribute(String attribute){ + String query = "SELECT "+attribute+", count(*) AS nbAtt FROM FpDataEntity Group By "+attribute; + + List result = withTransaction(em -> ((List) em.createQuery(query).getResultList())); + return result; + + } } diff --git a/website/app/models/ParsedFP.java b/website/app/models/ParsedFP.java index ed847fe..7b1a8da 100644 --- a/website/app/models/ParsedFP.java +++ b/website/app/models/ParsedFP.java @@ -14,13 +14,16 @@ public class ParsedFP { private String osVersion; private String language; private String timezone; + private String nbFonts; /* Pattern */ private static Pattern langP = Pattern.compile("^(\\S\\S)"); private static Pattern fireP = Pattern.compile("Firefox/(\\d*\\.\\d*)"); private static Pattern chromeP = Pattern.compile("Chrome/(\\d*\\.\\d*)"); - private static Pattern safariP = Pattern.compile("Version/(\\d*\\.\\d*) Safari"); + private static Pattern criOSP = Pattern.compile("CriOS/(\\d*\\.\\d*)"); + private static Pattern safariP = Pattern.compile("Version/(\\d*(\\.\\d*){1,2}) Safari"); private static Pattern IEP = Pattern.compile("MSIE (\\d*\\.\\d*)"); + private static Pattern edgeP = Pattern.compile("Edge/(\\d*\\.\\d*)"); private static Pattern operaP1 = Pattern.compile("Opera.*?Version/(\\d*\\.\\d*)"); private static Pattern operaP2 = Pattern.compile("OPR/(\\d*\\.\\d*)"); private static Pattern winP = Pattern.compile("Windows NT (\\d*\\.\\d*)"); @@ -45,97 +48,127 @@ public class ParsedFP { public ParsedFP(String ua) { - /* Browser */ - Matcher fireM = fireP.matcher(ua); - Matcher chromeM = chromeP.matcher(ua); - Matcher safariM = safariP.matcher(ua); - Matcher IEM = IEP.matcher(ua); - Matcher operaM1 = operaP1.matcher(ua); - Matcher operaM2 = operaP2.matcher(ua); - - if(fireM.find()) {//Firefox - String ver = fireM.group(1); - this.browser = "Firefox"; - this.browserVersion = ver; - } else if(operaM1.find()) {//Opera (old) - String ver = operaM1.group(1); - this.browser = "Opera"; - this.browserVersion = ver; - } else if(operaM2.find()){//Opera (new) - String ver = operaM2.group(1); - this.browser = "Opera"; - this.browserVersion = ver; - } else if(chromeM.find()){//Chrome - String ver = chromeM.group(1); - this.browser = "Chrome"; - this.browserVersion = ver; - } else if(safariM.find()){//Safari - String ver = safariM.group(1); - this.browser = "Safari"; - this.browserVersion = ver; - } else if(IEM.find()) {//IE8/9/10 - String ver = IEM.group(1); - this.browser = "IE"; - this.browserVersion = ver; - } else if(ua.contains("Trident/7.0; rv:11.0")) {//IE11 - this.browser = "IE"; - this.browserVersion = "11.0"; - } else {//Others + if (ua == null){ this.browser = "Others"; this.browserVersion = "Unknown"; - } - - /* OS */ - - if(ua.contains("Mobile")) { - Matcher androidM = androidP.matcher(ua); - Matcher iosM = iosP.matcher(ua); - Matcher winPhoneM = winPhoneP.matcher(ua); - - if(androidM.find()){ - String ver = androidM.group(1); - this.os = "Android"; - this.osVersion = ver; - } else if(ua.contains("Android")) { - this.os = "Android"; - this.osVersion = "Unknown"; - } else if(iosM.find()){ - String ver = iosM.group(1); - this.os = "iOS"; - this.osVersion = ver.replace("_","."); - } else if(winPhoneM.find()){ - String ver = winPhoneM.group(1); - this.os = "Windows Phone"; - this.osVersion = ver; + this.os = "Others"; + this.osVersion = "Desktop"; + } else { + /* Browser */ + Matcher fireM = fireP.matcher(ua); + Matcher chromeM = chromeP.matcher(ua); + Matcher criOSM = criOSP.matcher(ua); + Matcher safariM = safariP.matcher(ua); + Matcher edgeM = edgeP.matcher(ua); + Matcher IEM = IEP.matcher(ua); + Matcher operaM1 = operaP1.matcher(ua); + Matcher operaM2 = operaP2.matcher(ua); + + if (fireM.find()) {//Firefox + String ver = fireM.group(1); + this.browser = "Firefox"; + this.browserVersion = ver; + } else if (operaM1.find()) {//Opera (old) + String ver = operaM1.group(1); + this.browser = "Opera"; + this.browserVersion = ver; + } else if (operaM2.find()) {//Opera (new) + String ver = operaM2.group(1); + this.browser = "Opera"; + this.browserVersion = ver; + } else if (edgeM.find()){//Microsoft Edge + String ver = edgeM.group(1); + this.browser = "Edge"; + this.browserVersion = ver; + } else if (chromeM.find()) {//Chrome + String ver = chromeM.group(1); + this.browser = "Chrome"; + this.browserVersion = ver; + } else if (criOSM.find()){//Chrome sous iOS + String ver = criOSM.group(1); + this.browser = "Chrome"; + this.browserVersion = ver; + } else if (safariM.find()) {//Safari + String ver = safariM.group(1); + this.browser = "Safari"; + this.browserVersion = ver; + } else if (IEM.find()) {//IE8/9/10 + String ver = IEM.group(1); + this.browser = "IE"; + this.browserVersion = ver; + } else if (ua.contains("Trident/7.0;") && ua.contains("rv:11.0")) {//IE11 + this.browser = "IE"; + this.browserVersion = "11.0"; } else { - this.os = "Others"; - this.osVersion = "Mobile"; + this.browser = "Others"; + if (ua.contains("CPU") && ua.contains("AppleWebKit") && ua.contains("Mobile")){//iOS Web View + this.browserVersion = "iOS App"; + } else if (ua.contains("Android") && ua.contains("Safari")) {//Android Web View + this.browserVersion = "Android App"; + } else if (ua.contains("bot")) {//Bot + this.browserVersion = "Bot"; + } else if (ua.contains("FBAN")){//Facebook App + this.browserVersion = "Facebook App"; + } else {//Others + this.browserVersion = "Unknown"; + } } - } else { - Matcher winM = winP.matcher(ua); - Matcher macM = macP.matcher(ua); - if (winM.find()) {//Windows - String ver = winM.group(1); - this.os = "Windows"; - this.osVersion = windowsMap.get(ver); - } else if (macM.find()) {//Mac - String ver = macM.group(1); - this.os = "Mac"; - this.osVersion = ver.replace("_","."); - } else if (ua.contains("Linux")) {//Linux - this.os = "Linux"; - if (ua.contains("Ubuntu") || ua.contains("U;")) { - this.osVersion = "Ubuntu"; + /* OS */ + if (ua.contains("Mobile")) { + Matcher androidM = androidP.matcher(ua); + Matcher iosM = iosP.matcher(ua); + Matcher winPhoneM = winPhoneP.matcher(ua); + + if (androidM.find()) { + String ver = androidM.group(1); + this.os = "Android"; + this.osVersion = ver; + } else if (ua.contains("Android")) { + this.os = "Android"; + this.osVersion = "Unknown"; + } else if (iosM.find()) { + String ver = iosM.group(1); + this.os = "iOS"; + this.osVersion = ver.replace("_", "."); + } else if (winPhoneM.find()) { + String ver = winPhoneM.group(1); + this.os = "Windows Phone"; + this.osVersion = ver; } else { - this.osVersion = "Other distros"; + this.os = "Others"; + this.osVersion = "Mobile"; } - } else {//Others - this.os = "Others"; - if(ua.contains("bot")){ - this.osVersion = "Bot"; - } else { - this.osVersion = "Desktop"; + + } else { + Matcher winM = winP.matcher(ua); + Matcher macM = macP.matcher(ua); + if (winM.find()) {//Windows + String ver = winM.group(1); + this.os = "Windows"; + if(windowsMap.containsKey(ver)) { + this.osVersion = windowsMap.get(ver); + } else { + this.osVersion = "Unrecognised version"; + } + } else if (macM.find()) {//Mac + String ver = macM.group(1); + this.os = "Mac"; + this.osVersion = ver.replace("_", "."); + } else if (ua.contains("Linux")) {//Linux + this.os = "Linux"; + if (ua.contains("Ubuntu") || ua.contains("U;")) { + this.osVersion = "Ubuntu"; + } else { + this.osVersion = "Other distros"; + } + } else {//Others + this.os = "Others"; + if (ua.contains("bot")) { + this.osVersion = "Bot"; + } else { + this.osVersion = "Desktop"; + } } } } @@ -162,11 +195,15 @@ public String getLanguage() { } public void setLanguage(String languageHttp) { - Matcher langM = langP.matcher(languageHttp); - if(langM.find()) { - this.language = langM.group(1); + if(languageHttp == null){ + this.language = "Not communicated"; } else { - this.language = "Not communicated"; + Matcher langM = langP.matcher(languageHttp); + if (langM.find()) { + this.language = langM.group(1); + } else { + this.language = "Not communicated"; + } } } @@ -175,6 +212,33 @@ public String getTimezone() { } public void setTimezone(String timezone) { - this.timezone = timezone.replace("\"",""); + if(timezone == null){ + this.timezone = "Not specified"; + } else { + this.timezone = timezone.replace("\"", ""); + } + } + + public String getNbFonts() { + return nbFonts; + } + + public void setNbFonts(String fonts) { + if(fonts == null){ + this.nbFonts = "NC"; + } else { + Integer nbFonts = fonts.split("_").length; + if (nbFonts > 2) { + int step = 50; + int j = step; + while (j < nbFonts) { + j += step; + } + this.nbFonts = (j - step) + "-" + j; + } else { + this.nbFonts = "NC"; + } + } + } } diff --git a/website/app/models/RangeComparator.java b/website/app/models/RangeComparator.java new file mode 100644 index 0000000..69d8b14 --- /dev/null +++ b/website/app/models/RangeComparator.java @@ -0,0 +1,32 @@ +package models; + +import java.util.Comparator; + +public class RangeComparator implements Comparator { + + private RangeComparator(){}; + + private static RangeComparator INSTANCE = new RangeComparator(); + + public static RangeComparator getInstance(){ + return INSTANCE; + } + + @Override + public int compare(String str1, String str2){ + if (str1 == null) { + if(str2 == null){ + return 0; + } else { + return 1; + } + } else if (str2 == null) { + return -1; + } else { + String[] vals1 = str1.split("-"); + String[] vals2 = str2.split("-"); + return Integer.signum(Integer.valueOf(vals1[0]).compareTo(Integer.valueOf(vals2[0]))); + } + } + +} diff --git a/website/app/models/RangeMap.java b/website/app/models/RangeMap.java new file mode 100644 index 0000000..ad5365e --- /dev/null +++ b/website/app/models/RangeMap.java @@ -0,0 +1,9 @@ +package models; + +public class RangeMap extends CounterMap { + + public RangeMap() { + super(RangeComparator.getInstance()); + } +} + diff --git a/website/app/models/Rangemap.java b/website/app/models/Rangemap.java deleted file mode 100644 index 27ad773..0000000 --- a/website/app/models/Rangemap.java +++ /dev/null @@ -1,31 +0,0 @@ -package models; - -import java.util.Comparator; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicInteger; - -public class Rangemap extends TreeMap { - - public Rangemap() { - super(new RangeComparator()); - } - - public void add(String value){ - if(this.containsKey(value)){ - this.get(value).incrementAndGet(); - } else { - this.put(value,new AtomicInteger(1)); - } - } -} - -class RangeComparator implements Comparator { - - @Override - public int compare(String str1, String str2){ - String[] vals1 = str1.split("-"); - String[] vals2 = str2.split("-"); - return Integer.signum(Integer.valueOf(vals1[0]).compareTo(Integer.valueOf(vals2[0]))); - } - -} diff --git a/website/app/models/Stats.java b/website/app/models/Stats.java new file mode 100644 index 0000000..2282bce --- /dev/null +++ b/website/app/models/Stats.java @@ -0,0 +1,78 @@ +package models; + +import java.util.HashMap; +import java.sql.Timestamp; + + +public class Stats{ + + private Integer nbTotal; + private CounterMap timezone; + private HashMap browsers; + private HashMap os; + private VersionMap languages; + private RangeMap nbFonts; + + public Integer getNbTotal() { + return nbTotal; + } + + public CounterMap getTimezone() { + return timezone; + } + + public HashMap getBrowsers() { + return browsers; + } + + public HashMap getOs() { + return os; + } + + public VersionMap getLanguages() { + return languages; + } + + public RangeMap getNbFonts() { + return nbFonts; + } + + private Stats(){ + FpDataEntityManager em = new FpDataEntityManager(); + this.nbTotal = em.getNumberOfEntries(); + this.timezone = em.getTimezoneStats(); + HashMap> resMap = em.getOSBrowserStats(); + this.browsers = resMap.get("browsers"); + this.os = resMap.get("os"); + this.languages = em.getLanguageStats(); + this.nbFonts = em.getFontsStats(); + } + + public Stats(Timestamp tsl, Timestamp tsu){ + FpDataEntityManager em = new FpDataEntityManager(); + this.nbTotal = em.getNumberOfEntriesSinceDate(tsl, tsu); + this.timezone = em.getTimezoneStatsSinceDate(tsl, tsu); + HashMap> resMap = em.getOSBrowserStatsSinceDate(tsl, tsu); + this.browsers = resMap.get("browsers"); + this.os = resMap.get("os"); + this.languages = em.getLanguageStatsSinceDate(tsl, tsu); + this.nbFonts = em.getFontsStatsSinceDate(tsl, tsu); + } + + private static Stats INSTANCE = new Stats(); + + public static Stats getInstance(){ + return INSTANCE; + } + + public void addFingerprint(ParsedFP fp){ + nbTotal += 1; + timezone.add(fp.getTimezone()); + browsers.get(fp.getBrowser()).add(fp.getBrowserVersion()); + os.get(fp.getOs()).add(fp.getOsVersion()); + languages.add(fp.getLanguage()); + if(!fp.getNbFonts().equals("NC")) nbFonts.add(fp.getNbFonts()); + } + + +} diff --git a/website/app/models/Vermap.java b/website/app/models/VersionComparator.java similarity index 59% rename from website/app/models/Vermap.java rename to website/app/models/VersionComparator.java index a04321f..1b63eb8 100644 --- a/website/app/models/Vermap.java +++ b/website/app/models/VersionComparator.java @@ -1,37 +1,17 @@ package models; -import scala.xml.Atom; - import java.util.Comparator; -import java.util.TreeMap; -import java.util.concurrent.atomic.AtomicInteger; -public class Vermap extends TreeMap { +public class VersionComparator implements Comparator { - public Vermap() { - super(new VersionComparator()); - } + private VersionComparator(){}; - public void add(String value){ - if(this.containsKey(value)){ - this.get(value).incrementAndGet(); - } else { - this.put(value,new AtomicInteger(1)); - } - } + private static VersionComparator INSTANCE = new VersionComparator(); - public double getCounter(){ - double counter = 0; - for(AtomicInteger value : this.values()){ - counter += value.doubleValue(); - } - return counter; + public static VersionComparator getInstance(){ + return INSTANCE; } -} - -class VersionComparator implements Comparator { - private static Double doubleValue(String s) { try { return Double.valueOf(s); @@ -49,7 +29,7 @@ public int compareVersion(String str1, String str2){ } if (i < vals1.length && i < vals2.length){ - int diff = Integer.valueOf(vals1[i]).compareTo(Integer.valueOf(vals2[i])); + int diff = Double.valueOf(vals1[i]).compareTo(Double.valueOf(vals2[i])); return Integer.signum(diff); } else { return Integer.signum(vals1.length - vals2.length); diff --git a/website/app/models/VersionMap.java b/website/app/models/VersionMap.java new file mode 100644 index 0000000..1970623 --- /dev/null +++ b/website/app/models/VersionMap.java @@ -0,0 +1,10 @@ +package models; + +public class VersionMap extends CounterMap { + + public VersionMap() { + super(VersionComparator.getInstance()); + } + +} + diff --git a/website/app/views/about.scala.html b/website/app/views/about.scala.html index e809a5d..71cb380 100644 --- a/website/app/views/about.scala.html +++ b/website/app/views/about.scala.html @@ -1,125 +1,27 @@ - - - - - Am I unique? - - - - - - - - - - - - - -
- - - - -
- -
- - -
-
-
-
- -
-
-

Who maintains this web site?

- This web site is created and maintained by a team of researchers, who investigates the software monocultures and software - diversity on the web. The research team is financially supported by the - DIVERSIFY European project and by a grant - from the INSA-Rennes school. - - The site is hosted at the INRIA Rennes - Bretagne-Atlantique research center and the - IRISA lab. - -

What is the purpose of this web site?

- -

Inform users about their position within the web ecosystem

- - With this web site, we aim at providing users with some basic information about their configuration and - how trackable it is. We also want to exploit the data we collect to advise users about how they can be more - similar to others and thus be less trackable. +@header = { +} +@footer = { +} + +@main(header, footer , "/about") { + +
+
+
+
-

Investigate the diversity of fingerprints

+ +
+
- The research question we investigate is: how can we automatically reconfigure a user's platform to - fuzz the device fingerprint in a realistic manner? The degree of "realistic"-ness is important to prevent users - of our solution to be detected as such. The collection of a large base of real fingerprints will allow us to tune our - solution towards platforms that are as different as possible yet not distinguishable as "strange" configurations. + @Html(Messages("about")) -
-
- - - -
-
-
+
+
+ + +
- -
- -
- - - - - - +} \ No newline at end of file diff --git a/website/app/views/differences.scala.html b/website/app/views/differences.scala.html new file mode 100644 index 0000000..0bc027f --- /dev/null +++ b/website/app/views/differences.scala.html @@ -0,0 +1,21 @@ +@import models._; + +@(fps : TreeSet[FpDataEntity], differences : HashMap[Integer, String], tabHtmlDifferences : HashMap[Integer, String]) + + + @for(fp <- fps.iterator) { + @if(differences.get(fp.getCounter()) != null){ +

+ + @Html(tabHtmlDifferences.get(fp.getCounter())) + @if(differences.get(fp.getCounter()).equals("nodiff")){ + + } +
@Messages("fp.detAtt")@Messages("history.before")@Messages("history.after")@Messages("history.differences")
@Messages("history.nodiff")
+

+
+
+
+ } + } + diff --git a/website/app/views/extension.scala.html b/website/app/views/extension.scala.html new file mode 100644 index 0000000..ea12c23 --- /dev/null +++ b/website/app/views/extension.scala.html @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + +

Extension AmIUnique

+
+
+
+
+ + + diff --git a/website/app/views/faq.scala.html b/website/app/views/faq.scala.html index db06496..b55285a 100644 --- a/website/app/views/faq.scala.html +++ b/website/app/views/faq.scala.html @@ -1,171 +1,103 @@ - - - - - Am I unique? - - - - - - - - - - - - - -
- - - - -
- -
- - -
-
-
-
- - -

What is browser fingerprinting?

-

- Device fingerprinting or browser fingerprinting is the systematic collection of - information about a remote device, for identification purposes. Client-side scripting - languages allow the development of procedures to collect very rich fingerprints: browser - and operating system type and version, screen resolution, architecture type, lists of fonts, - plugins, microphone, camera, etc. -

- - On this site, we collect: -
    -
  • the User agent header
  • -
  • the Accept header
  • -
  • the Connection header
  • -
  • the Encoding header
  • -
  • the Language header
  • -
  • the list of plugins
  • -
  • the platform
  • -
  • the cookies preferences (allowed or not)
  • -
  • the Do Not Track preferences (yes, no or not communicated)
  • -
  • the timezone
  • -
  • the screen resolution and its color depth
  • -
  • the use of local storage
  • -
  • the use of session storage
  • -
  • a picture rendered with the HTML Canvas element
  • -
  • a picture rendered with WebGL
  • -
  • the presence of AdBlock
  • -
  • the list of fonts
  • -
- -

How is the fingerprint collected?

- Browser fingerprints are also called - “cookieless monsters” because it is not necessary to install any form of cookie to collect a fingerprint. - This means that the act of fingerprinting a specific browser is stateless and transparent for the user. - Any third-party interested in fingerprinting can exploit a set of different techniques to get a rich fingerprint: -
    -
  • The user agent and the - accept headers are automatically sent to websites when a connection is initiated.
  • -
  • JavaScript gives access to many browser-populated features - like the plugins installed on the user’s device.
  • -
  • If the Flash plugin is installed, its rich programming - interface (API) provides access to many system-specific attributes: exact version of the operating system, list of fonts, - screen resolution, timezone.
  • -
  • Through the display of an HTML5 Canvas element, it is possible to collect small differences in the hardware or in the software - configurations, thanks to slight differences in the image rendering between devices. The smallest pixel difference can be detected. - This is called canvas fingerprinting .
  • -
- On this site, we use: -
    -
  • Plugin Detect for plugins detection in some versions of Internet Explorer.
  • -
  • three.js for the rendering of a WebGL scene.
  • -
- -

How are the fingerprints exploited?

- Like all tracking technology, it is a double-edge sword.
- Fingerprints can be used in a constructive way to combat fraud or credential hijacking, - by checking that a user who logs into a specific site is likely the legitimate user.
- Fingerprints can also be used in more questionable way, in order to track users across - web sites and collect information about their habits and their tastes without the users knowing about it.
- Fingerprints can even be used in a destructive way: if attackers know which software modules - (specific browser version, plugins, etc.) are installed on a specific device, they can deliver - exploits that are tailored for these specific modules or combination of modules. - -

What is the difference between amiunique.org and other similar web sites?

- - There exist other sites that collect and / or inform users about the amount of information that can be collected through their website: - - -

amiunique.org is different from these sites, with respect to the following points. - It implements state of the art fingerprinting techniques. In particular, this is the first web site that collects information through WebGL. - It provides different levels of information to the user. Beyond the complete fingerprint, amiunique is the only site that provides a synthetic view, - which gives few, easy to understand statistics about the degree of unicity of a device among the sample we have observed so far. We also centralize - a number of different pointers about fingerprinting, which go from general pages that introduce the technique, to technical papers in the research - literature and press articles that cover the topic. - -

- - -
- -
- - - - - - \ No newline at end of file + + +

What is browser fingerprinting?

+

+ Device fingerprinting or browser fingerprinting is the systematic collection of + information about a remote device, for identification purposes. Client-side scripting + languages allow the development of procedures to collect very rich fingerprints: browser + and operating system type and version, screen resolution, architecture type, lists of fonts, + plugins, microphone, camera, etc. +

+ + On this site, we collect: +
    +
  • the User agent header
  • +
  • the Accept header
  • +
  • the Connection header
  • +
  • the Encoding header
  • +
  • the Language header
  • +
  • the list of plugins
  • +
  • the platform
  • +
  • the cookies preferences (allowed or not)
  • +
  • the Do Not Track preferences (yes, no or not communicated)
  • +
  • the timezone
  • +
  • the screen resolution and its color depth
  • +
  • the use of local storage
  • +
  • the use of session storage
  • +
  • a picture rendered with the HTML Canvas element
  • +
  • a picture rendered with WebGL
  • +
  • the presence of AdBlock
  • +
  • the list of fonts
  • +
+ +

How is the fingerprint collected?

+ Browser fingerprints are also called + “cookieless monsters” because it is not necessary to install any form of cookie to collect a fingerprint. + This means that the act of fingerprinting a specific browser is stateless and transparent for the user. + Any third-party interested in fingerprinting can exploit a set of different techniques to get a rich fingerprint: +
    +
  • The user agent and the + accept headers are automatically sent to websites when a connection is initiated.
  • +
  • JavaScript gives access to many browser-populated features + like the plugins installed on the user’s device.
  • +
  • If the Flash plugin is installed, its rich programming + interface (API) provides access to many system-specific attributes: exact version of the operating system, list of fonts, + screen resolution, timezone.
  • +
  • Through the display of an HTML5 Canvas element, it is possible to collect small differences in the hardware or in the software + configurations, thanks to slight differences in the image rendering between devices. The smallest pixel difference can be detected. + This is called canvas fingerprinting .
  • +
+ On this site, we use: +
    +
  • Plugin Detect for plugins detection in some versions of Internet Explorer.
  • +
  • three.js for the rendering of a WebGL scene.
  • +
+ +

How are the fingerprints exploited?

+ Like all tracking technology, it is a double-edge sword.
+ Fingerprints can be used in a constructive way to combat fraud or credential hijacking, + by checking that a user who logs into a specific site is likely the legitimate user.
+ Fingerprints can also be used in more questionable way, in order to track users across + web sites and collect information about their habits and their tastes without the users knowing about it.
+ Fingerprints can even be used in a destructive way: if attackers know which software modules + (specific browser version, plugins, etc.) are installed on a specific device, they can deliver + exploits that are tailored for these specific modules or combination of modules. + +

What is the difference between amiunique and https://panopticlick.eff.org?

+ + amiunique shares some goals with panoticlick, but it provides a number of novelties: +
    +
  • amiunique implements the most recent techniques for fingerprinting, including webGL and canvas
  • +
  • amiunique provides more information to the users, including global statistics, as well as a concise summary of the main characteristics of a browser
  • +
  • amiunique is open source and available on Github
  • +
+ +

What is the difference between amiunique.org and other similar web sites?

+ + There exist other sites that collect and / or inform users about the amount of information that can be collected through their website: + + +

amiunique.org is different from these sites, with respect to the following points. + It implements state of the art fingerprinting techniques. In particular, this is the first web site that collects information through WebGL. + It provides different levels of information to the user. Beyond the complete fingerprint, amiunique is the only site that provides a synthetic view, + which gives few, easy to understand statistics about the degree of unicity of a device among the sample we have observed so far. We also centralize + a number of different pointers about fingerprinting, which go from general pages that introduce the technique, to technical papers in the research + literature and press articles that cover the topic. +

+ +} diff --git a/website/app/views/fp.scala.html b/website/app/views/fp.scala.html index 5b79fce..141fb82 100644 --- a/website/app/views/fp.scala.html +++ b/website/app/views/fp.scala.html @@ -1,59 +1,91 @@ @(implicit request: play.mvc.Http.Request) - - - - - Am I unique? - - - - - - - - - - - - - - @if(request.getHeader("User-Agent").contains("MSIE 8.0")) { - - - + +@header = { + + + + + + + + + + + + + + + + + + + - - - - - - - - - - - - - - - + + + - - -
- - - -
- -
- - - -
-
- - -
- -
- - -
- - -
- - - \ No newline at end of file + + setTimeout(sendData,1000); + + } + + //Script to force the fingerprinting + if(readCookie('tempFp') != null){ + setTimeout(function() { + language = $('#languageHttpVal').text(); + language = language.split(";"); + + $('@Messages("fp.btnforcefp")').insertAfter("#graBut"); + + $('#forceFp').css('margin-left','3px'); + + $( "#forceFp" ).mouseover(function() { + $('


@Messages("fp.forcefp")

').insertAfter("#forceFp"); + + $('#infoforcefp').css('font-weight','bold'); + $('#infoforcefp').css('color','#149c82'); + }); + + $( "#forceFp" ).mouseleave(function() { + $('#infoforcefp').remove(); + }); + + $("#forceFp").click(function(){ + createCookie('tempFp', 1, -1); + createCookie('tempPerc', 1, -1); + if(domLocalStorage == "yes"){ + localStorage.setItem("tempFpContent", ""); + } + + location.reload(); + }); + + }, 500); + } + }); + +} + +@footer = { +} + +@main(header, footer , "/fp") { +
+
+ + +
+} \ No newline at end of file diff --git a/website/app/views/fpNoJs.scala.html b/website/app/views/fpNoJs.scala.html index 9f9fe24..932adf5 100644 --- a/website/app/views/fpNoJs.scala.html +++ b/website/app/views/fpNoJs.scala.html @@ -1,252 +1,188 @@ @import com.fasterxml.jackson.databind.JsonNode -@(fp : JsonNode)(pFp: ParsedFP)(per : JsonNode)(os: JsonNode)(brow: JsonNode)(lang: JsonNode)(nbTotal: Integer)(nbIdent: Integer) - - - - - - Am I unique? - - - - - - - - - - - - - -
- - - -
- -

@Messages("fp.title")

- - - @if(nbIdent<2) { -

@Messages("fp.track1")

- } else { - @if(nbIdent/per.get("nbTotal").doubleValue() < 0.05) { -

@Messages("fp.track2")

- } else { -

@Messages("fp.track3")

- } - } - - - @if(pFp.getBrowser() != "Others"){ -

@{"%.2f".format(per.get(pFp.getBrowser()).doubleValue())} @Messages("fp.browser1") @pFp.getBrowser()@Messages("fp.browser2")

-

@{"%.2f".format(brow.get(pFp.getBrowser()).get(pFp.getBrowserVersion()).doubleValue()*100/nbTotal)} - @Messages("fp.browser1") @pFp.getBrowser() @pFp.getBrowserVersion()@Messages("fp.browser2")

- } else { -

@{"%.2f".format(per.get(pFp.getBrowser()).doubleValue())} @Messages("fp.unknownBrow")

- } - - - @if(pFp.getOs() != "Others") { -

@{"%.2f".format(per.get(pFp.getOs()).doubleValue())} @Messages("fp.os1") @pFp.getOs()@Messages("fp.os2")

- @if(pFp.getOsVersion() != "Other distros") { -

@{"%.2f".format(os.get(pFp.getOs()).get(pFp.getOsVersion()).doubleValue() * 100 / nbTotal)} @Messages("fp.os1") - @pFp.getOs() @pFp.getOsVersion()@Messages("fp.os2")

- } - } else { -

@{"%.2f".format(per.get(pFp.getOs()).doubleValue())} @Messages("fp.unknownOs")

- } - - - @if(pFp.getLanguage() != "Not communicated") { -

@{"%.2f".format(lang.get(pFp.getLanguage()).doubleValue() * 100 / nbTotal)} @Messages("fp.lang1") - "@pFp.getLanguage()"@Messages("fp.lang2")

- } - - - @if(nbIdent<2) { -

@Html(Messages("fp.uniq1")) @("%.0f".format(per.get("nbTotal").doubleValue())) @Messages("fp.uniq2")

- } else { -

@Html(Messages("fp.notuniq1")) @nbIdent @Messages("fp.notuniq2") @("%.0f".format(per.get("nbTotal").doubleValue())) - @Messages("fp.notuniq3") (@{"%.2f".format(nbIdent*100.0/nbTotal)} @Messages("fp.notuniq4")

- } - - -

@Messages("fp.detailsTitle")

- - - - - - - - - - @display(column : String) = { - @defining(per.get(column).doubleValue()) { value => - @if(value == -1) { - - } else { - @if(value >= 0.1) { - - } else { - - } - } - - } - } - - - - @display(column = "userAgentHttp") - - - - @display(column = "acceptHttp") - - - - - @display(column = "connectionHttp") - - - - - @display(column = "encodingHttp") - - - - - @display(column = "languageHttp") - - - - - @display(column = "pluginsJs") - - - - - @display(column = "platformJs") - - - - - @display(column = "cookiesJs") - - - - - @display(column = "dntJs") - - - - - @display(column = "timezoneJs") - - - - - @display(column = "resolutionJs") - - - - - @display(column = "localJs") - - - - - @display(column = "sessionJs") - - - - - @display(column = "canvasJs") - - - - - @display(column = "webGljs") - - - - - @display(column = "fontsFlash") - - - - - @display(column = "resolutionFlash") - - - - - @display(column = "languageFlash") - - - - - @display(column = "platformFlash") - - - - - @display(column = "adBlock") - - - -
@Messages("fp.detAtt")@Messages("fp.detSim")@Messages("fp.detVal")
Unique@("%.2f".format(value))%<0.1%@fp.get(column)
@Messages("fp.userAgent")
@Messages("fp.accept")
@Messages("fp.connection")
@Messages("fp.encoding")
@Messages("fp.language")
@Messages("fp.plugins")
@Messages("fp.platformJ")
@Messages("fp.cookies")
@Messages("fp.dnt")
@Messages("fp.timezone")
@Messages("fp.resolutionJ")
@Messages("fp.local")
@Messages("fp.session")
@Messages("fp.canvas")
@Messages("fp.webGl")
@Messages("fp.fonts")
@Messages("fp.resolutionF")
@Messages("fp.languageF")
@Messages("fp.platformF")
@Messages("fp.adBlock")
- - -
- - -
- - - - \ No newline at end of file +@(fp : JsonNode)(pFp: ParsedFP)(per : JsonNode)(os: JsonNode)(brow: JsonNode)(lang: JsonNode)(nbTotal: Double)(nbIdent: Integer) + +@display(column : String) = { + @defining(per.get(column).doubleValue()) { value => + @if(value == -1) { + Unique + } else { + @if(value >= 0.1) { + @("%.2f".format(value))% + } else { + <0.1% + } + } + @fp.get(column) + } +} + +@header = { +} +@footer = { +} + +@main(header, footer , "/fp") { + +

@Messages("fp.title")

+ + + @if(nbIdent<2) { +

@Messages("fp.track1")

+ } else { + @if(nbIdent/nbTotal< 0.05) { +

@Messages("fp.track2")

+ } else { +

@Messages("fp.track3")

+ } + } + + + @if(pFp.getBrowser() != "Others"){ +

@{"%.2f".format(per.get(pFp.getBrowser()).doubleValue())} @Messages("fp.browser1") @pFp.getBrowser()@Messages("fp.browser2")

+

@{"%.2f".format(brow.get(pFp.getBrowser()).get(pFp.getBrowserVersion()).doubleValue()*100/nbTotal)} + @Messages("fp.browser1") @pFp.getBrowser() @pFp.getBrowserVersion()@Messages("fp.browser2")

+ } else { +

@{"%.2f".format(per.get(pFp.getBrowser()).doubleValue())} @Messages("fp.unknownBrow")

+ } + + + @if(pFp.getOs() != "Others") { +

@{"%.2f".format(per.get(pFp.getOs()).doubleValue())} @Messages("fp.os1") @pFp.getOs()@Messages("fp.os2")

+ @if(pFp.getOsVersion() != "Other distros") { +

@{"%.2f".format(os.get(pFp.getOs()).get(pFp.getOsVersion()).doubleValue() * 100 / nbTotal)} @Messages("fp.os1") + @pFp.getOs() @pFp.getOsVersion()@Messages("fp.os2")

+ } + } else { +

@{"%.2f".format(per.get(pFp.getOs()).doubleValue())} @Messages("fp.unknownOs")

+ } + + + @if(pFp.getLanguage() != "Not communicated") { +

@{"%.2f".format(lang.get(pFp.getLanguage()).doubleValue() * 100 / nbTotal)} @Messages("fp.lang1") + "@pFp.getLanguage()"@Messages("fp.lang2")

+ } + + + @if(nbIdent<2) { +

@Html(Messages("fp.uniq1")) @("%.0f".format(nbTotal)) @Messages("fp.uniq2")

+ } else { +

@Html(Messages("fp.notuniq1")) @nbIdent @Messages("fp.notuniq2") @("%.0f".format(nbTotal)) + @Messages("fp.notuniq3") (@{"%.2f".format(nbIdent*100.0/nbTotal)} @Messages("fp.notuniq4")

+ } + + +

@Messages("fp.detailsTitle")

+ + + + + + + + + + + + + @display(column = "userAgentHttp") + + + + @display(column = "acceptHttp") + + + + + @display(column = "encodingHttp") + + + + + @display(column = "languageHttp") + + + + + @display(column = "pluginsJs") + + + + + @display(column = "platformJs") + + + + + @display(column = "cookiesJs") + + + + + @display(column = "dntJs") + + + + + @display(column = "timezoneJs") + + + + + @display(column = "resolutionJs") + + + + + @display(column = "localJs") + + + + + @display(column = "sessionJs") + + + + + @display(column = "canvasJs") + + + + + + + + + + + + + + + + + @display(column = "fontsFlash") + + + + + @display(column = "resolutionFlash") + + + + + @display(column = "languageFlash") + + + + + @display(column = "platformFlash") + + + + + @display(column = "adBlock") + + + +
@Messages("fp.detAtt")@Messages("fp.detSim")@Messages("fp.detVal")
@Messages("fp.userAgent")
@Messages("fp.accept")
@Messages("fp.encoding")
@Messages("fp.language")
@Messages("fp.plugins")
@Messages("fp.platformJ")
@Messages("fp.cookies")
@Messages("fp.dnt")
@Messages("fp.timezone")
@Messages("fp.resolutionJ")
@Messages("fp.local")
@Messages("fp.session")
@Messages("fp.canvas")
@Messages("fp.vendorWebGl")@fp.get("vendorWebGljs")
@Messages("fp.rendererWebGl")@fp.get("rendererWebGljs")
@Messages("fp.fonts")
@Messages("fp.resolutionF")
@Messages("fp.languageF")
@Messages("fp.platformF")
@Messages("fp.adBlock")
+} \ No newline at end of file diff --git a/website/app/views/history.scala.html b/website/app/views/history.scala.html new file mode 100644 index 0000000..7078755 --- /dev/null +++ b/website/app/views/history.scala.html @@ -0,0 +1,234 @@ +@import java.util._; +@import models._; +@import org.apache.commons.lang3.time.DateFormatUtils; + +@(fps : TreeSet[FpDataEntity]) + +@header = { + + + + + + + +} +@footer = { +} + +@main(header, footer, "/history") { + +

@Messages("history.title")

+ +

+ @Messages("history.warning") +

+ +
+
+ +
+ +
+ +
+
+ + + + + + + + +
+ +
+ + +} + diff --git a/website/app/views/home.scala.html b/website/app/views/home.scala.html index 5c43bc6..47ceced 100644 --- a/website/app/views/home.scala.html +++ b/website/app/views/home.scala.html @@ -1,94 +1,37 @@ - - - - - Am I unique? - - - - - - - - - - - - - -
- - - - -
- -
- - - @Html(Messages("home")) - -
- - -
- - -
- +@header = { +} +@footer = { - - - - \ No newline at end of file +} + +@main(header, footer, "/home") { +
+
+ + +
+ @Html(Messages("home")) +
+} \ No newline at end of file diff --git a/website/app/views/links.scala.html b/website/app/views/links.scala.html index e8b11fc..05501e1 100644 --- a/website/app/views/links.scala.html +++ b/website/app/views/links.scala.html @@ -1,144 +1,116 @@ - - - - - Am I unique? +@header = { +} +@footer = { +} + +@main(header, footer, "/links") { +

General definitions of fingerprinting

+ + + +

Fingerprinting in the press

+ + + +

amiunique.org in the press

+ + + +

Technical presentation

+ + +

Scientific papers

+ + +

Other sites about fingerprinting

+ +} - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/website/app/views/main.scala.html b/website/app/views/main.scala.html new file mode 100644 index 0000000..74db7ac --- /dev/null +++ b/website/app/views/main.scala.html @@ -0,0 +1,144 @@ +@(header : Html = Html(""), footer : Html = Html(""), pagePath : String)(content: Html) +@active(path: String) = @{ + if (pagePath.startsWith(path)) + Html("class='active'") + else + "" +} + + + + + + Am I unique? + + + + + + + + + + + + + + + + + + + + @header + + + + + + + + + + + + + @footer + + + + + \ No newline at end of file diff --git a/website/app/views/noTimeline.scala.html b/website/app/views/noTimeline.scala.html new file mode 100644 index 0000000..78772a1 --- /dev/null +++ b/website/app/views/noTimeline.scala.html @@ -0,0 +1,88 @@ +@header = { +} + +@footer = { +} + +@main(header, footer, "/timeline") { +

@Messages("timeline.title")

+
+
+
+
+ +
+
+ @Messages("timeline.noTimeline") +

+
+ +
+
+
+


+ + +
+ + +} \ No newline at end of file diff --git a/website/app/views/privacy.scala.html b/website/app/views/privacy.scala.html index 1ecc172..8f3b58c 100644 --- a/website/app/views/privacy.scala.html +++ b/website/app/views/privacy.scala.html @@ -1,184 +1,9 @@ - - - - - Am I unique? +@header = { +} +@footer = { +} - - - - - - +@main(header, footer, "/privacy") { + @Html(Messages("privacy")) +} - - - - - -
- - - - -
- -
- - - - -
-

The goal of this project is to measure and study the diversity of web browsers and establish statistical - profiles of browser fingerprints. All of the data for the project will be collected in an anonymized - form which ensures that it is not Personally Identifiable Information*, nor otherwise likely to lead to - the identification or tracking of any web users.

-

*Personally identifiable information (PII) is any data that could potentially identify a specific individual.

- -

We are committed to protecting the privacy of visitors to our website. In this policy, "we" refers to the INRIA - researchers and students who are bound to keep information they receive confidential.

- - -

Information Gathered by the amiunique Website

-

For the needs of the research project defined here, amiunique collects anonymous data about the - configuration of computers, operating systems, browsers and plugins. If you click the "View my fingerprint" button, this - type of information will be collected from your browser. Although these kinds of data may form a `fingerprint' that - could in principle be combined with information about page requests and identifying details in order to track people's - browsing habits, we will never do so.

-

The specific `fingerprint' information we collect is: -

    -
  • the User agent header
  • -
  • the Accept header
  • -
  • the Connection header
  • -
  • the Encoding header
  • -
  • the Language header
  • -
  • the list of plugins
  • -
  • the platform
  • -
  • the cookies preferences (allowed or not)
  • -
  • the Do Not Track preferences (yes, no or not communicated)
  • -
  • the timezone
  • -
  • the screen resolution and its color depth
  • -
  • the use of local storage
  • -
  • the use of session storage
  • -
  • a picture rendered with the HTML Canvas element
  • -
  • a picture rendered with WebGL
  • -
  • the presence of AdBlock
  • -
  • the list of fonts
  • -
-

In addition to these data, we collect several kinds of `housekeeping' information to assist us in analyzing the - fingerprint data. The housekeeping information is: -

    -
  • Cookies
  • -
  • Encrypted IP addresses
  • -
  • Timestamps
  • -
-

Our practices and purposes for collecting these housekeeping records are discussed below: -

Cookies

-

amiunique sets a cookie that persists for 4 months for the purpose of determining how often browser characteristics change, and - how often they stay the same, when a browser returns over time. If your browser is configured to accept cookies, and you return to - amiunique several times, the cookie will be used to link the data from your visits together so that we can study the natural evolution of - browser fingerprints. - If you want to disable/enable browser cookies, click on the link below corresponding to your browser for instructions. - You should be aware that entirely disabling cookies may block some interactive features on some websites (most notably, automatic logins). - -

Instructions for: -

- Moreover, if you are interested in enabling/disabling Flash cookies, you can do so by visiting the following page: - Manage - Flash cookies. - -

IP addresses

- amiunique does not log IP addresses, but we do compute hashes of each IP address. This hashed IP will allow us to collect an anonymous dataset - about how often browsers that change IP address could have been followed using a fingerprint. - -

Timestamps

- amiunique collects a "fuzzy" timestamp, rounded to the nearest hour, each time it is visited. This will be used to measure how fast browser - fingerprints change, but for no other purpose. - -

Our Use of Information from amiunique

- We will analyze the collected data in order to establish what are realistic profiles for browser fingerprints. The purpose of this analysis is - to set an automatic procedure to proactively diversify user platforms in a realistic manner. - amiunique has no Third-Party Service Providers. -

Sharing of amiunique data

- We may publish or share aggregated, statistical data from the amiunique project in order to facilitate privacy research, educate people about - privacy problems, and to aid in the development of privacy-enhancing technologies. We have gone to great lengths to ensure that amiunique does - not produce any records about anyone's browsing habits or the identities of any individual visitors, so we will never be in a position where we - could share any such records. -

Security

- In a general way, INRIA commits to carry out technical and organizational means to protect all information we gathered against illegal or - fortuitous destruction, fortuitous loss, alteration, diffusion or unauthorized access. Nevertheless, INRIA shall be required to divulgate any - information to comply with any applicable law or rules, or to respond to any administrative or judiciary order. - - -

Although we make good faith efforts to store information collected in a secure operating environment, we cannot guarantee complete security. - Information collected will be maintained for a length of time appropriate to the needs of this research project. -
-

This Privacy Policy is based on some material from the EFF website (Electronic Frontier Foundation), which is freely redistributed under the - Creative Commons Attribution License. - -

- - -
- - -
- - - - - - diff --git a/website/app/views/results.scala.html b/website/app/views/results.scala.html index 4606abf..9c0782d 100644 --- a/website/app/views/results.scala.html +++ b/website/app/views/results.scala.html @@ -1,28 +1,39 @@ @import com.fasterxml.jackson.databind.JsonNode -@(fp : JsonNode)(pFp: ParsedFP)(per : JsonNode)(os: JsonNode)(brow: JsonNode)(lang: JsonNode)(timezone: JsonNode)(nbTotal: Integer)(nbIdent: Integer) +@(fp : JsonNode)(pFp: ParsedFP)(per : JsonNode)(os: JsonNode)(brow: JsonNode)(lang: JsonNode)(timezone: JsonNode)(nbTotal: Double)(nbIdent: Integer)(counter: String) + +
+

@Messages("fp.detailsTitle")

- - - - - - - - - - @display(column : String) = { - @defining(per.get(column).doubleValue()) { value => - @if(value == -1) { - - } else { - @if(value >= 0.1) { - - } else { - - } - } + +
+
@Messages("fp.detAtt")@Messages("fp.detSim") @Messages("fp.detVal")
Unique@("%.2f".format(value))%<0.1%
+ + + + + + + + + @display(column : String) = { + } - } - - - - @display(column = "userAgentHttp") - - - - @display(column = "acceptHttp") - - - - - @display(column = "connectionHttp") - - - - - @display(column = "encodingHttp") - - - - - @display(column = "languageHttp") - - - - - @display(column = "pluginsJs") - - - - - @display(column = "platformJs") - - - - - @display(column = "cookiesJs") - - - - - @display(column = "dntJs") - - - - - @display(column = "timezoneJs") - - - - - @display(column = "resolutionJs") - - - - - @display(column = "localJs") - - - - - @display(column = "sessionJs") - - - - - @display(column = "canvasJs") - - - - - @display(column = "webGljs") - - - - - @display(column = "fontsFlash") - - - - - @display(column = "resolutionFlash") - - - - - @display(column = "languageFlash") - - - - - @display(column = "platformFlash") - - - - - @display(column = "adBlock") - - - -
@Messages("fp.detAtt")@Messages("fp.detSim") @Messages("fp.detVal")
@fp.get(column)
@Messages("fp.userAgent")
@Messages("fp.accept")
@Messages("fp.connection")
@Messages("fp.encoding")
@Messages("fp.language")
@Messages("fp.plugins")
@Messages("fp.platformJ")
@Messages("fp.cookies")
@Messages("fp.dnt")
@Messages("fp.timezone")
@Messages("fp.resolutionJ")
@Messages("fp.local")
@Messages("fp.session")
@Messages("fp.canvas")
@Messages("fp.webGl")
@Messages("fp.fonts")
@Messages("fp.resolutionF")
@Messages("fp.languageF")
@Messages("fp.platformF")
@Messages("fp.adBlock")
+ + + @Messages("fp.userAgent") + @display(column = "userAgentHttp") + + + @Messages("fp.accept") + @display(column = "acceptHttp") + + + + @Messages("fp.encoding") + @display(column = "encodingHttp") + + + + @Messages("fp.language") + @display(column = "languageHttp") + + + + @Messages("fp.plugins") + @display(column = "pluginsJs") + + + + @Messages("fp.platformJ") + @display(column = "platformJs") + + + + @Messages("fp.cookies") + @display(column = "cookiesJs") + + + + @Messages("fp.dnt") + @display(column = "dntJs") + + + + @Messages("fp.timezone") + @display(column = "timezoneJs") + + + + @Messages("fp.resolutionJ") + @display(column = "resolutionJs") + + + + @Messages("fp.local") + @display(column = "localJs") + + + + @Messages("fp.session") + @display(column = "sessionJs") + + + + @Messages("fp.canvas") + @display(column = "canvasJs") + + + + @Messages("fp.vendorWebGl") + @display(column = "vendorWebGljs") + + + + @Messages("fp.rendererWebGl") + @display(column = "rendererWebGljs") + + + + @Messages("fp.fonts") + @display(column = "fontsFlash") + + + + @Messages("fp.resolutionF") + @display(column = "resolutionFlash") + + + + @Messages("fp.languageF") + @display(column = "languageFlash") + + + + @Messages("fp.platformF") + @display(column = "platformFlash") + + + + @Messages("fp.adBlock") + @display(column = "adBlock") + + + +
@@ -388,7 +393,7 @@

@Messages("fp.graphsTitle")

/* Language */ var langData = '@{Html(lang.toString())}'; - langData = $.parseJSON(langData); + langData = $.parseJSON(langData.replace(/""/g,'"')); var langArray = []; $.each(langData, function(key, lang) { @@ -439,9 +444,9 @@

@Messages("fp.graphsTitle")

timeData = $.parseJSON(timeData); var timeArray = []; $.each(timeData, function(key, tab){ - var name = tab[0]; - if(tab[0] != "no JS") { - var utc = parseInt(tab[0],10)/-60; + var name = key; + if(key != "no JS") { + var utc = parseInt(key,10)/-60; if(utc<0){ utc = "UTC" + utc ; } else { @@ -450,7 +455,7 @@

@Messages("fp.graphsTitle")

} else { var utc = "Not specified"; } - var per = tab[1]*100/nbTotal; + var per = tab*100/nbTotal; if(name == '@pFp.getTimezone()'){ timeArray.push({name:utc, y:per, color:'#90ed7d', sliced: true}); } else { diff --git a/website/app/views/stats.scala.html b/website/app/views/stats.scala.html index ffbf959..5cdee37 100644 --- a/website/app/views/stats.scala.html +++ b/website/app/views/stats.scala.html @@ -1,411 +1,414 @@ @import com.fasterxml.jackson.databind.JsonNode -@(nbTotal: Integer)(nbUnique: Integer)(timezone: JsonNode)(browsers: JsonNode)(os: JsonNode)(languages: JsonNode)(fonts: JsonNode) +@(nbTotal: Integer)(timezone: JsonNode)(browsers: JsonNode)(os: JsonNode)(languages: JsonNode)(fonts: JsonNode)(datel: String = "")(dateu: String = "")(typereq: String= "") +@active(typer: String) = @{ + if (typereq.equals(typer)) + Html("class='btn btn-success'") + else + Html("class='btn btn-primary'") +} - - - - - Am I unique? +@displayCustom() = @{ + if (typereq.equals("custom")) + Html("style='display:block'") + else + Html("style='display:none'") +} - - - - - - +@header = { + + + + + + - - + - - - - - - - + + +} +@footer = { +} -
+@main(header, footer, "/stats") { + +
+
+
+
+ -
+

Fingerprints

+

@Messages("stats.nbentries") : @nbTotal

- -
-
-
-
- + -

Fingerprints

-

Number of entries : @nbTotal

+
+
+ + +
-
-
-
-
-
+
+ + +
- -
+ /* Fonts */ + var fontsData = '@{ + Html(fonts.toString()) + }'; + fontsData = $.parseJSON(fontsData); + var catArray = []; + var seriesArray = []; + $.each(fontsData, function(key, value){ + catArray.push(key); + seriesArray.push(value); + }); + $('#fontsGraph').highcharts({ + chart: { + type: 'column' + }, + title: { + text: 'Distribution of number of fonts' + }, + xAxis: { + categories: catArray + }, + yAxis: { + min: 0, + title: { + text: 'Number of fingerprints' + } + }, + tooltip: { + headerFormat: '{point.y} fingerprints have {point.key} fonts.', + pointFormat: '', + shared: true, + useHTML: true + }, + plotOptions: { + column: { + pointPadding: 0.2, + borderWidth: 0 + } + }, + series: [{ + name: 'Number', + data: seriesArray + }] + }); -
- -
- - \ No newline at end of file + + } +} \ No newline at end of file diff --git a/website/app/views/timeline.scala.html b/website/app/views/timeline.scala.html new file mode 100644 index 0000000..ee54a55 --- /dev/null +++ b/website/app/views/timeline.scala.html @@ -0,0 +1,238 @@ +@import com.fasterxml.jackson.databind.JsonNode +@(fp: JsonNode, tabHtmlDifferences : JsonNode, startDate : JsonNode, endDate: JsonNode, firstDate: String, currentDate: String) + +@header = { + + + + + + +} +@footer = { + + +} + +@main(header, footer, "/timeline") { +

@Messages("timeline.title")

+ + + +
+
+
+
+
+
+
+ @timelineTable(fp) +
+
+} \ No newline at end of file diff --git a/website/app/views/timelineEmpty.scala.html b/website/app/views/timelineEmpty.scala.html new file mode 100644 index 0000000..10d1b7b --- /dev/null +++ b/website/app/views/timelineEmpty.scala.html @@ -0,0 +1,35 @@ +@import com.fasterxml.jackson.databind.JsonNode +@(fp : JsonNode) + + +@header = { +} + +@footer = { + +} + +@main(header, footer, "/timeline") { +

@Messages("timeline.title")

+ + + +
+
+
+

@Html(Messages("timeline.empty"))

+
+
+ @timelineTable(fp) +
+
+ +} \ No newline at end of file diff --git a/website/app/views/timelineTable.scala.html b/website/app/views/timelineTable.scala.html new file mode 100644 index 0000000..6e8be09 --- /dev/null +++ b/website/app/views/timelineTable.scala.html @@ -0,0 +1,146 @@ +@import com.fasterxml.jackson.databind.JsonNode +@(fp : JsonNode) + +@display(column : String) = { + @fp.get(column) +} + + + +
+ + + + + + + + + + + + + @display(column = "userAgentHttp") + + + + @display(column = "acceptHttp") + + + + + @display(column = "encodingHttp") + + + + + @display(column = "languageHttp") + + + + + @display(column = "pluginsJs") + + + + + @display(column = "platformJs") + + + + + @display(column = "cookiesJs") + + + + + @display(column = "dntJs") + + + + + @display(column = "timezoneJs") + + + + + @display(column = "resolutionJs") + + + + + @display(column = "localJs") + + + + + @display(column = "sessionJs") + + + + + @display(column = "canvasJs") + + + + + @display(column = "vendorWebGljs") + + + + + @display(column = "rendererWebGljs") + + + + + @display(column = "fontsFlash") + + + + + @display(column = "resolutionFlash") + + + + + @display(column = "languageFlash") + + + + + @display(column = "platformFlash") + + + + + @display(column = "adBlock") + + +
@Messages("fp.detAtt")@Messages("fp.detVal")
@Messages("fp.userAgent")
@Messages("fp.accept")
@Messages("fp.encoding")
@Messages("fp.language")
@Messages("fp.plugins")
@Messages("fp.platformJ")
@Messages("fp.cookies")
@Messages("fp.dnt")
@Messages("fp.timezone")
@Messages("fp.resolutionJ")
@Messages("fp.local")
@Messages("fp.session")
@Messages("fp.canvas")
@Messages("fp.vendorWebGl")
@Messages("fp.rendererWebGl")
@Messages("fp.fonts")
@Messages("fp.resolutionF")
@Messages("fp.languageF")
@Messages("fp.platformF")
@Messages("fp.adBlock")
+
+ + + diff --git a/website/app/views/tools.scala.html b/website/app/views/tools.scala.html new file mode 100644 index 0000000..c43b924 --- /dev/null +++ b/website/app/views/tools.scala.html @@ -0,0 +1,196 @@ +@header = { +} +@footer = { +} + +@main(header, footer , "/tools") { + +

+ @Messages("pvtools.desc") +

+

@Messages("pvtools.ext")

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@Messages("pvtools.colext")@Messages("pvtools.coldesc")@Messages("pvtools.colsite")
uBlock Origin@Messages("pvtools.ublock") + + + + + +
Ghostery@Messages("pvtools.ghostery") + + + + + +
HTTPS Everywhere@Messages("pvtools.httpseverywhere1")
+ @Messages("pvtools.httpseverywhere2") + @Messages("pvtools.httpseverywhere3") +
+ + + + + +
Lightbeam@Messages("pvtools.lightbeam1")
+ + @Messages("pvtools.lightbeam2") + + @Messages("pvtools.lightbeam3") +
+ + + +
AdBlock Plus@Messages("pvtools.adblock1") + + @Messages("pvtools.adblock2") + + + + + + +
Disconnect@Messages("pvtools.disconnect") + + + + + +
Privacy Badger@Messages("pvtools.privacybadger") + + + + + +
NoScript@Messages("pvtools.noscript") + + + +
Self-Destructing Cookies@Messages("pvtools.selfdest") + +
+
+ +

@Messages("pvtools.searchengine")

+ DuckDuckGo - + + @Messages("pvtools.webpage") +
+ @Messages("pvtools.ddg1") + @Messages("pvtools.ddg2") + @Messages("pvtools.ddg3")

+ + @Messages("pvtools.tor1") - + + @Messages("pvtools.webpage") +
+ @Messages("pvtools.tor2")

+ + Tails - + + @Messages("pvtools.webpage") +
+ @Messages("pvtools.tails")

+ + TrackOFF - + + @Messages("pvtools.webpage") +
+ @Messages("pvtools.trackOff")
+ +

@Messages("pvtools.ip1")

+ @Messages("pvtools.ip2")
+ @Messages("pvtools.ip3") + @Messages("pvtools.ip4") @Messages("pvtools.ip5") + +

@Messages("pvtools.cookies1")

+ @Messages("pvtools.cookies2") + @Messages("pvtools.cookies3") + @Messages("pvtools.cookies4") @Messages("pvtools.cookies5").
+ @Messages("pvtools.cookies6") + ( @Messages("pvtools.cookies3") + @Messages("pvtools.cookies7") @Messages("pvtools.cookies5")) + @Messages("pvtools.cookies8") + + + +} \ No newline at end of file diff --git a/website/app/views/viewFP.scala.html b/website/app/views/viewFP.scala.html new file mode 100644 index 0000000..63f6b62 --- /dev/null +++ b/website/app/views/viewFP.scala.html @@ -0,0 +1,196 @@ +@(implicit request: play.mvc.Http.Request) + +@header = { + + + + + + + + + + + + + + + + + +} + +@footer = { +} + +@main(header, footer , "/viewFP") { +

@Messages("fp.detailsTitle")

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
@Messages("fp.detAtt")@Messages("fp.detVal")
@Messages("fp.userAgent") @request.getHeader("User-Agent")
@Messages("fp.accept") @request.getHeader("Accept")
@Messages("fp.encoding") @request.getHeader("Accept-Encoding")
@Messages("fp.language") @request.getHeader("Accept-Language")
@Messages("fp.plugins")
@Messages("fp.platformJ")
@Messages("fp.cookies")
@Messages("fp.dnt")
@Messages("fp.timezone")
@Messages("fp.resolutionJ")
@Messages("fp.local")
@Messages("fp.session")
@Messages("fp.canvas")
@Messages("fp.vendorWebGl")
@Messages("fp.rendererWebGl")
@Messages("fp.fonts")
@Messages("fp.resolutionF")
@Messages("fp.languageF")
@Messages("fp.platformF")
@Messages("fp.adBlock")
+
+ + +
+} \ No newline at end of file diff --git a/website/build.sbt b/website/build.sbt index e0beaa6..a3ce154 100644 --- a/website/build.sbt +++ b/website/build.sbt @@ -4,7 +4,7 @@ version := "1.0-SNAPSHOT" lazy val root = (project in file(".")).enablePlugins(PlayJava) -scalaVersion := "2.11.1" +scalaVersion := "2.11.6" libraryDependencies ++= Seq( javaJdbc, diff --git a/website/conf/META-INF/persistence.xml b/website/conf/META-INF/persistence.xml index 5400d08..aab450c 100644 --- a/website/conf/META-INF/persistence.xml +++ b/website/conf/META-INF/persistence.xml @@ -11,21 +11,22 @@ - - + + - - + + - - + + - - + + + diff --git a/website/conf/application.conf b/website/conf/application.conf index 5ea77c0..ef74631 100644 --- a/website/conf/application.conf +++ b/website/conf/application.conf @@ -9,7 +9,7 @@ # # See http://www.playframework.com/documentation/latest/ApplicationSecret for more details. # Run "play-generate-secret" to regenerate the application secret -application.secret="" +application.secret="APl/4zd:ciVDDR_vgEbiCFLTXNrHgQ01Ew4V9H:0H0jAHhi/f;04Sx`=zE2D7hPC" # The application languages # ~~~~~ @@ -40,9 +40,8 @@ application.langs="en,fr" db.default.driver=com.mysql.jdbc.Driver db.default.url="jdbc:mysql://localhost/fingerprint" -db.default.user="" -db.default.pass="" -db.default.autocommit=false +db.default.user="root" +db.default.pass="bdd" jpa.default=defaultPersistenceUnit @@ -79,3 +78,5 @@ logger.play=INFO # Logger provided to your application: logger.application=DEBUG +akka.default-dispatcher.core-pool-size-max = 64 +akka.debug.receive = on diff --git a/website/conf/messages.en b/website/conf/messages.en index ef595a4..c41953e 100644 --- a/website/conf/messages.en +++ b/website/conf/messages.en @@ -1,38 +1,191 @@ -home=
\ -
\ -

Learn how identifiable you are on the Internet

\ -

Help us investigate the diversity of web browsers

\ -
\ -
\ -
\ -
\ +home=

Learn how identifiable you are on the Internet

\ +

Help us investigate the diversity of web browsers

\ +
\ +
\ +

View my browser fingerprint

\
\ -
\ -

View my browser fingerprint

\ +
\
\

By clicking on this button, only anonymous data will be collected and a cookie will be stored in your browser for four months.\ You can find more details in the Privacy Policy.

\
\
\ -
\ -
\ -

\ - Spread the word! Share AmIUnique!
\ - Try it on all your devices!

\ - \ - \ - \ - \ - \ - \ - \ -

\ -
\ -
\ -

What is browser fingerprinting? Learn more

\ -

Any questions? Send us an email at contact@amiunique.org

\ -
\ -
+

\ + Spread the word! Share AmIUnique!
\ + Try it on all your devices!

\ + \ + \ + \ + \ + \ + \ + \ +

\ +
\ +
\ +

What is browser fingerprinting? Learn more

\ +

Any questions? Send us an email at contact@amiunique.org

+ +home.milestone1 = We are now over 400,000 fingerprints!!! Thanks to all visitors for your continuous support!
\ + We also have an AmIUnique extension for +home.milestone2 = and +home.milestone3 = . More details can be found +home.milestone4= HERE +home.milestone5= Our friends in another research team are currently studying the detectability of browser extensions. You can help them +home.pub1 = Our first scientific publication using the AmIUnique dataset is available +home.pub2 = HERE + +about=

Who maintains this web site?

\ + This web site is created and maintained by a team of researchers, who investigates the software monocultures and software\ + diversity on the web. The research team is financially supported by the\ + DIVERSIFY European project and by a grant\ + from the INSA-Rennes school.\ + The site is hosted at the INRIA Rennes\ + Bretagne-Atlantique research center and the\ + IRISA lab.\ +

What is the purpose of this web site?

\ +

Inform users about their position within the web ecosystem

\ + With this web site, we aim at providing users with some basic information about their configuration and\ + how trackable it is. We also want to exploit the data we collect to advise users about how they can be more\ + similar to others and thus be less trackable.\ +

Investigate the diversity of fingerprints

\ + The research question we investigate is: how can we automatically reconfigure a user's platform to\ + fuzz the device fingerprint in a realistic manner? The degree of realism is important to prevent users\ + of our solution to be detected as such. The collection of a large base of real fingerprints will allow us to tune our\ + solution towards platforms that are as different as possible yet not distinguishable as "strange" configurations. + +privacy=

The goal of this project is to measure and study the diversity of web browsers and establish statistical\ + profiles of browser fingerprints. All of the data for the project will be collected in an anonymized\ + form which ensures that it is not Personally Identifiable Information*, nor otherwise likely to lead to\ + the identification or tracking of any web users.

\ +

*Personally identifiable information (PII) is any data that could potentially identify a specific individual.

\ +

We are committed to protecting the privacy of visitors to our website. In this policy, "we" refers to the INRIA\ + researchers and students who are bound to keep information they receive confidential.

\ +

Information Gathered by the AmIUnique Website

\ +

For the needs of the research project defined here, AmIUnique collects anonymous data about the\ +configuration of computers, operating systems, browsers and plugins. If you click the "View my fingerprint" button, this\ +type of information will be collected from your browser. Although these kinds of data may form a `fingerprint' that\ +could in principle be combined with information about page requests and identifying details in order to track people's\ +browsing habits, we will never do so.

\ +

The specific `fingerprint' information we collect is:\ +

    \ +
  • \ +
    the User agent header
    \ +
    HTTP header sent to the server that contains information regarding your browser and operating system
    \ +
  • \ +
  • \ +
    the Accept header
    \ +
    HTTP header sent to the server that contains information regarding the type of media that are acceptable for the response
    \ +
  • \ +
  • \ +
    the Connection header
    \ +
    HTTP header sent to the server that contains specific options that are desired for that particular connection
    \ +
  • \ +
  • \ +
    the Encoding header
    \ +
    HTTP header sent to the server that lists the compression methods supported by the browser
    \ +
  • \ +
  • \ +
    the Language header
    \ +
    HTTP header sent to the server that indicates the preferred languages for the response
    \ +
  • \ +
  • \ +
    the list of plugins
    \ +
    Browser-populated JavaScript attribute that gives the list of activated plugins in the browser (window.navigator.plugins)
    \ +
  • \ +
  • \ +
    the platform
    \ +
    Browser-populated JavaScript attribute that indicates the platform the browser is running on (window.navigator.platform)
    \ +
  • \ +
  • \ +
    the cookies preferences (allowed or not)
    \ +
    Browser-populated JavaScript attribute that indicates if the browser accepts cookies or not (window.navigator.cookieEnabled)
    \ +
  • \ +
  • \ +
    the Do Not Track preferences (yes, no or not communicated)
    \ +
    Browser-populated JavaScript attribute that indicates your Do Not Track setting (window.navigator.doNotTrack), "NC" means the value was not specified
    \ +
  • \ +
  • \ +
    the timezone
    \ +
    Timezone offset of your browser obtainable through JavaScript (new Date().getTimezoneOffset())
    \ +
  • \ +
  • \ +
    the screen resolution and its color depth
    \ +
    Browser-populated JavaScript attributes that indicate the resolution of the device’s screen (window.screen.height/width/colorDepth)
    \ +
  • \ +
  • \ +
    the use of local storage
    \ +
    JavaScript test to find out if local storage is supported (storage of a specific value in "localStorage")
    \ +
  • \ +
  • the use of session storage
    \ +
    JavaScript test to find out if session storage is supported (storage of a specific value in "sessionStorage")
    \ +
  • \ +
  • a picture rendered with the HTML Canvas element
    \ +
    Rendering of a specific picture with the HTML5 Canvas element following a fixed set of instructions. The picture presents some slight noticeable variations depending on the OS and the browser used.
    \ +
  • \ +
  • \ +
    a picture rendered with WebGL
    \ +
    Rendering of specific 3D forms following a fixed set of instructions. The picture presents some slight noticeable variations depending on the device of the user.
    \ +
  • \ +
  • \ +
    the presence of AdBlock
    \ +
    Test to find out if the AdBlock extension is installed
    \ +
  • \ +
  • \ +
    the list of fonts
    \ +
    Flash attribute that gives the entire list of fonts installed on the operating system (flash.text.Font.enumerateFonts(true))
    \ +
  • \ +
\ +

In addition to these data, we collect several kinds of `housekeeping' information to assist us in analyzing the\ + fingerprint data. The housekeeping information is:\ +

    \ +
  • Cookies
  • \ +
  • Encrypted IP addresses
  • \ +
  • Timestamps
  • \ +
\ +

Our practices and purposes for collecting these housekeeping records are discussed below:\ +

Cookies

\ +

AmIUnique sets a cookie that persists for 4 months for the purpose of determining how often browser characteristics change, and\ +how often they stay the same, when a browser returns over time. If your browser is configured to accept cookies, and you return to\ +AmIUnique several times, the cookie will be used to link the data from your visits together so that we can study the natural evolution of\ +browser fingerprints.\ +If you want to disable/enable browser cookies, click on the link below corresponding to your browser for instructions.\ +You should be aware that entirely disabling cookies may block some interactive features on some websites (most notably, automatic logins).\ +

Instructions for:\ +

\ +Moreover, if you are interested in enabling/disabling Flash cookies, you can do so by visiting the following page:\ +Manage\ + Flash cookies.\ +

IP addresses

\ +AmIUnique does not log IP addresses, but we do compute hashes of each IP address. This hashed IP will allow us to collect an anonymous dataset\ +about how often browsers that change IP address could have been followed using a fingerprint.\ +

Timestamps

\ +AmIUnique collects a "fuzzy" timestamp, rounded to the nearest hour, each time it is visited. This will be used to measure how fast browser\ +fingerprints change, but for no other purpose.\ +

Our Use of Information from AmIUnique

\ +We will analyze the collected data in order to establish what are realistic profiles for browser fingerprints. The purpose of this analysis is\ +to set an automatic procedure to proactively diversify user platforms in a realistic manner.\ +AmIUnique has no Third-Party Service Providers.\ +

Sharing of AmIUnique data

\ +We may publish or share aggregated, statistical data from the AmIUnique project in order to facilitate privacy research, educate people about\ +privacy problems, and to aid in the development of privacy-enhancing technologies. We have gone to great lengths to ensure that AmIUnique does\ +not produce any records about anyone's browsing habits or the identities of any individual visitors, so we will never be in a position where we\ +could share any such records.\ +

Security

\ +In a general way, INRIA commits to carry out technical and organizational means to protect all information we gathered against illegal or\ +fortuitous destruction, fortuitous loss, alteration, diffusion or unauthorized access. Nevertheless, INRIA shall be required to divulgate any\ +information to comply with any applicable law or rules, or to respond to any administrative or judiciary order.\ +

Although we make good faith efforts to store information collected in a secure operating environment, we cannot guarantee complete security.\ + Information collected will be maintained for a length of time appropriate to the needs of this research project.\ +
\ +

This Privacy Policy is based on some material from the EFF website (Electronic Frontier Foundation), which is freely redistributed under the\ + Creative Commons Attribution License. fp.overview=Overview fp.details=Details @@ -57,8 +210,9 @@ fp.lang2=as their primary language, as yours. fp.time1=% of observed browsers have fp.time2=as their timezone, as yours. -fp.uniq1=You have the only browser out of -fp.uniq2=with this fingerprint. +fp.uniq1=However, your full fingerprint is unique among the +fp.uniq2=collected so far. +fp.uniq3=Want to know why? Click here fp.notuniq1=But only fp.notuniq2=browsers out of the @@ -67,8 +221,11 @@ fp.notuniq4=%) have exactly the same fingerprint as yours. fp.btn1=View more details fp.btn2=View graphs +fp.btnforcefp= Force fingerprinting +fp.forcefp=We use a temporary cookie of 3 minutes in order to keep your fingerprint and load it faster. If you have made change and would like to see them, click on Force Fingerprinting. fp.detailsTitle=My fingerprint +fp.detProgress=Ratio calculations in progress... fp.detAtt=Attribute fp.detSim=Similarity ratio fp.detSimExp=Percentage of fingerprints sharing the same value for each attribute @@ -83,6 +240,7 @@ fp.connection=Connection fp.encoding=Content encoding fp.language=Content language fp.plugins=List of plugins +fp.detailPlugins=Detail of the plugins fp.platformJ=Platform fp.cookies=Cookies enabled fp.dnt=Do Not Track @@ -92,6 +250,8 @@ fp.local=Use of local storage fp.session=Use of session storage fp.canvas=Canvas fp.webGl=WebGL +fp.vendorWebGl=WebGL Vendor +fp.rendererWebGl=WebGL Renderer fp.fonts=List of fonts fp.resolutionF=Screen resolution fp.languageF=Language @@ -113,8 +273,140 @@ fp.localExp=JavaScript test to find out if local storage is supported (storage o fp.sessionExp=JavaScript test to find out if session storage is supported (storage of a specific value in "sessionStorage") fp.canvasExp=Rendering of a specific picture with the HTML5 Canvas element following a fixed set of instructions. The picture presents some slight noticeable variations depending on the OS and the browser used. fp.webGlExp=Rendering of specific 3D forms following a fixed set of instructions. The picture presents some slight noticeable variations depending on the device of the user. +fp.vendorWebGlExp=Name of the WebGL Vendor. Some browsers give the full name of the underlying graphics card used by the device. +fp.rendererWebGlExp=Name of the WebGL Renderer. Some browsers give the full name of the underlying graphics driver. fp.fontsExp=Flash attribute that gives the entire list of fonts installed on the operating system (flash.text.Font.enumerateFonts(true)) fp.resolutionFExp=Flash attribute that indicates the resolution of the device’s screen (flash.system.Capabilities. screenResolutionX/Y). This resolution can differ from the JavaScript one, mostly in the presence of a multi-screen setup. fp.languageFExp=Flash attribute that indicates the primary language of the browser (flash.system.Capabilities.language) fp.platformFExp=Flash attribute that indicates the platform the browser is running on (flash.system.Capabilities.os). This value can give even more information in comparison with the value given by JavaScript. -fp.adBlockExp=Test to find out if the AdBlock extension is installed \ No newline at end of file +fp.adBlockExp=Test to find out if the AdBlock extension is installed + + +history.title=My history +history.warning=On this page, you can visualize and compare all of your fingerprints. If one of your fingerprints does not appear, it is due to the fact that it had not evolved between two consecutive visits. Therefore, we did not store it in the database. +history.subtitle1=Fingerprint of the +history.subtitle2=at +history.subtitle3=h +history.nodiff=Your fingerprint has no difference with the previous one. +history.before=Before +history.after=After +history.differences=Differences +history.warningmodal=Warning +history.warningmessage=You must select at least 2 checkboxes. +history.compare= Compare +history.close = Close +history.btn1 = History of my fingerprints +history.btn2 = Compare my fingerprints + +menu.home=Home +menu.myfp=My fingerprint +menu.globalstats= Global statistics +menu.history=My history +menu.faq = FAQ +menu.privpolicy = Privacy policy +menu.privtools = Privacy tools +menu.timeline=My timeline +menu.links= Links +menu.about = About +menu.github=View on GitHub +menu.new = New +menu.upd = Updated + +stats.nbentries = Number of entries +stats.zero=There was no entry in the database during the selected period. Please choose another one. +stats.beg=Start date +stats.end=End date +stats.btn1= All time +stats.btn2= Past month +stats.btn3= Past week +stats.btn4= Custom range +stats.btn5= Send + +modal.title=You are back +modal.body1=You can go on the +modal.body2= page to get your new fingerprint, then on the page +modal.body3= to visualize the evolution of your fingerprint since your last visit. +modal.close=Close +modal.history=Get your new fingerprint + +timeline.title = AmIUnique timeline +timeline.noTimeline = To understand the evolution of fingerprints and study it in depth, we have developed an extension for \ + Chrome and Firefox that checks your fingerprint every day for any changes. \ + If you are curious about your own fingerprint or if you want to help us investigate the diversity of fingerprints,\ + install the extension now! +timeline.img1 = The extension notifies you when your fingerprint has changed. +timeline.img2 = See the evolution of your browser fingerprint on your own timeline! +timeline.img3 = Get a direct access to your current browser fingerprint. +timeline.menu1 = My timeline +timeline.menu2 = My current fingerprint +timeline.empty = A second fingerprint is needed to be able to use the timeline feature.
\ + Come back in a few days or wait for the extension to notify you of any evolution and you will see what has been changed! + +timeline.headline = Your fingerprint's evolution +timeline.text =

You can find here all the evolution of your browser fingerprint through time.

Explore the timeline below!

+timeline.fp = Fingerprint +timeline.current = current fingerprint +timeline.evo1 = Evolution from fingerprint +timeline.evo2 = to +timeline.lang = en +timeline.noFlash = Flash not detected +timeline.attribute = Attribute +timeline.before = Before +timeline.after = After +timeline.diff = Difference + + +pvtools.desc = On this page, you will find links to tools that can help improve your privacy on the Internet. \ + With respect to fingerprinting, the best solutions that exist today are to simply block tracking scripts.\ + We cannot recommend spoofers because there is a risk that fingerprinters can detect such spoofing\ + techniques quite easily, which would quickly identify you as a liar.\ + Because the number of spoofers is likely low, your other discriminating \ + data (e.g. fonts and plugins) should be more than sufficient to \ + fingerprint and track you. +pvtools.rec = Recommended! + +pvtools.ext = Browser extensions +pvtools.colext = Extension +pvtools.coldesc = Description +pvtools.colsite = Website +pvtools.ublock = An efficient ad and tracker blocker with a small performance footprint! +pvtools.ghostery = Protect your privacy by blocking trackers on the Web and by learning who is watching you! +pvtools.httpseverywhere1 = Encrypt the web! Enable HTTPS automatically on websites that are known to support it. A project by the EFF (Electronic Frontier Foundation). +pvtools.httpseverywhere2 = This extension includes an option to verify SSL certificates directly by +pvtools.httpseverywhere3 = the EFF SSL Observatory. +pvtools.lightbeam1 = Visualize in details the servers you are contacting when you are surfing on the Internet! Developed by Mozilla. +pvtools.lightbeam2 = Presentation of Lightbeam +pvtools.lightbeam3 = by Gary Kovacs, former CEO of Mozilla, in a TED talk +pvtools.adblock1 = Block advertisements, trackers and more! We recommend the use of additional lists like the +pvtools.adblock2 = Fanboy Complete AdBlock list. +pvtools.disconnect = Stop tracking by third-party sites and visualize who is tracking you! +pvtools.privacybadger = Block spying ads and invisible trackers! A project by the EFF (Electronic Frontier Foundation). +pvtools.noscript = Take control of what is running in your browser by blocking unwanted scripts! +pvtools.selfdest = Remove cookies that are no longer used as soon as you close a tab! +pvtools.searchengine = Search engine, browser and operating systems +pvtools.webpage = Official webpage +pvtools.ddg1 = "The search engine that doesn't track you." Launched in 2008, DuckDuckGo differentiates itself from other search engines by emphasizing the protection of searchers' privacy and avoiding +pvtools.ddg2 = the filter bubble +pvtools.ddg3 = of personalized search results. DuckDuckGo does not collect or share personal information. +pvtools.tor1 = Tor and the Tor browser +pvtools.tor2 = The Tor network is a group of servers aimed at improving users' anonymity by routing all their traffic through a dedicated network. The Tor browser is based on Firefox and it uses the Tor network. It has been heavily modified to limit \ + as most as possible known fingerprinting techniques so that users share a unique fingerprint (even if in practice, the reality is a little bit different). +pvtools.tails = Based around the Tor browser and the Tor network, Tails (The Amnesic Incognito Live System) goes a step further by providing the exact same environment to its users. The operating system has been heavily modified so that it does not leave \ + a single trace on the computer. With the Tor browser, these are the strongest tools against fingerprinting at the price of a decline in usability. +pvtools.trackOff = TrackOFF, a Baltimore-based startup, has launched in mid-2015 a software suite that detects threats and changes your browser fingerprint to improve your digital privacy. Running on Windows for Chrome and Firefox, a trial is available \ + to test this new service. +pvtools.ip1 = IP address +pvtools.ip2 = Contrary to what people may think, modifying the IP address has no impact on fingerprinting since no addresses are collected to form your fingerprint. Your computer can still be identified and tracked on several browsing sessions. On its own, \ + the IP address is still a really strong form of identification since it may hardly vary over time depending on your internet service provider. Modifying your IP address is a good way to cover your tracks and hide your true location when you are browsing the web. +pvtools.ip3 = The most widespread way to change your IP address is to use a VPN (Virtual Private Network) service that will route securely all your network traffic through a trusted server. The +pvtools.ip4 = Best VPN website +pvtools.ip5 = has a rich list of VPN providers with detailed reviews about their strengths and weaknesses. +pvtools.cookies1 = Cookies +pvtools.cookies2 = Cookies can be used in addition to fingerprinting to track users on the web and identify with ease returning visitors. One way to counter unwanted tracking is to either limit authorized cookies or block cookies altogether. One recommended\ + setting is to never accept cookies from third-party sites since it is one of the main way for advertisers to track you. You will find instructions on how to block them on +pvtools.cookies3 = Firefox +pvtools.cookies4 = and +pvtools.cookies5 = Chrome +pvtools.cookies6 = For the more privacy conscious users among us, you can completely block them in the browser settings +pvtools.cookies7 = or +pvtools.cookies8 = or use an extension like Self-Destructing Cookies as mentioned in the browser extensions section. The only downside to these techniques is that you have to whitelist sites if you still want to authorize cookies on some on them. \ No newline at end of file diff --git a/website/conf/messages.fr b/website/conf/messages.fr index d364d11..5b7a897 100644 --- a/website/conf/messages.fr +++ b/website/conf/messages.fr @@ -1,46 +1,170 @@ -home=
\ -
\ -

Apprenez à quel point votre navigateur est unique

\ -

Aidez-nous à analyser la diversité des navigateurs web

\ -
\ -
\ -
\ -
\ +home=

Apprenez à quel point votre navigateur est unique

\ +

Aidez-nous à analyser la diversité des navigateurs web

\ +
\ +
\ +

Voir l’empreinte de mon navigateur

\
\ -
\ -

Voir l’empreinte de mon navigateur

\ +
\
\ -

En cliquant sur ce bouton, nous ne collectons que des données anonymes et nous installons un cookie sur votre navigateur pour 4 mois.\ +

Si vous cliquez sur ce bouton, nous ne collecterons que des données anonymes et nous installerons un cookie sur votre navigateur pour 4 mois.\ Plus de détails sont disponibles dans la politique de stockage et de protection des données.

\
\
\ -
\ -
\ -

\ - Partagez AmIUnique !
\ - Essayez aussi avec un smartphone ou une tablette !

\ - \ - \ - \ - \ - \ - \ - \ -

\ -
\ -
\ -
\ -

Qu’est-ce que l’empreinte d’un navigateur ? Apprenez-en plus

\ -

Des questions ? Écrivez-nous à contact@amiunique.org

\ -
\ -
+

\ + Partagez AmIUnique !
\ + Essayez aussi avec un smartphone ou une tablette !

\ + \ + \ + \ + \ + \ + \ + \ +

\ +
\ +
\ +
\ +

Qu’est-ce que l’empreinte d’un navigateur ? Apprenez-en plus

\ +

Des questions ? Écrivez-nous à contact@amiunique.org

+ +home.milestone1 = Nous avons dépassé les 400,000 fingerprints !!! Merci à tous les visiteurs pour votre contribution à nos recherches !
\ + Pour les plus curieux, nous avons aussi l’extension AmIUnique pour +home.milestone2 = et +home.milestone3 =. Plus de détails +home.milestone4 = ICI +home.milestone5=Une autre équipe INRIA étudie actuellement la détection d’extensions de navigateur. Vous pouvez en savoir plus +home.pub1 = Notre première publication issue des données d’AmIUnique est disponible +home.pub2 = ICI + +about=

Qui gère et maintient ce site web ?

\ + Ce site web a été créé et il est maintenu par une équipe de chercheurs qui étudient les monocultures et la diversité logicielles sur le Web. L’équipe de recherche est financée par le projet européen DIVERSIFY et par une bourse de thèse de l’INSA-Rennes.\ + Ce site est hébergé par le pôle de recherche Bretagne-Atlantique de l’INRIA-Rennes et le labo IRISA.\ +

Quels sont les objectifs de ce site web ?

\ +

Informer les utilisateurs de leur situation relative dans l’écosystème du Web

\ + Avec ce site web, nous cherchons à donner à l’utilisateur des éléments d’information basiques sur leur configuration et le degré de traçabilité de celle-ci. Nous souhaitons également exploiter les données que nous collectons pour conseiller les utilisateurs et leur permettre de ressembler aux autres et, de ce fait, être beaucoup moins facile à pister.\ +

Mener une étude sur la diversité des empreintes numériques

\ + La problématique de notre recherche est la suivante : comment pouvons-nous reconfigurer automatiquement la plateforme d’un utilisateur pour «flouter» son empreinte numérique de façon réaliste ? Un certain degré de réalisme est en effet important pour éviter que les utilisateurs de nos solutions soient détectés comme cherchant à déjouer le pistage. La récolte à grande échelle d’empreintes numériques véritables nous permettra d’orienter notre solution vers des plateformes aussi différentes que possible et cependant pas identifiables comme des configurations «bizarres». + +privacy=

Le but de ce projet est de mesurer et d’étudier la diversité des navigateurs Web et d’établir des profils statistiques de leurs empreintes numériques. Toutes les données destinées au projet seront collectées sous forme anonyme pour avoir la certitude qu’elles ne comprennent aucune information personnellement identifiable*, ni susceptible de conduire à l’identification ou au pistage d’un quelconque utilisateur du web.

\ +

*Une information personnellement identifiable est une donnée quelconque qui pourrait éventuellement mener à identifier un utilisateur spécifique.

\ +

Nous nous engageons à protéger la vie privée des visiteurs de notre site web. Dans cette politique, « nous » désigne les chercheurs et les étudiants de l’INRIA qui sont tenus de garder confidentielles les informations qu’ils reçoivent.

\ +

Informations récoltées par le site web AmIUnique

\ +

Pour les besoins du projet de recherche définis sur cette page, le site AmIUnique recueille des données anonymes sur la configuration des ordinateurs, systèmes d’exploitation, navigateurs et plugins. Si vous cliquez sur le bouton « Voir l’empreinte de mon navigateur », des informations de ce type seront recueillies à partir de votre navigateur. Ces types de données pourraient en principe constituer une « empreinte numérique » et être combinées avec des informations sur les requêtes de page et des éléments d’identification afin de pister les habitudes de navigation des utilisateurs, mais nous ne le ferons jamais.

\ +

Les informations particulières sur votre empreinte sont les suivantes :\ +

    \ +
  • Les en-têtes HTTP envoyés au serveur :
  • \ +
      \ +
      \ +
    • \ +
      User-agent
      \ +
      contient des informations sur le navigateur et le système d’exploitation utilisés
      \ +
    • \ +
    • \ +
      Accept
      \ +
      contient des options spécifiques relatives à la connexion en cours
      \ +
    • \ +
    • \ +
      Connection
      \ +
      indique le type de média attendu dans la réponse du serveur
      \ +
    • \ +
    • \ +
      Encoding
      \ +
      liste les méthodes de compression supportées par le navigateur
      \ +
    • \ +
    • \ +
      Language
      \ +
      indique les préférences linguistiques de l’utilisateur
      \ +
    • \ +
      \ +
    \ +
  • \ +
    La liste des plugins
    \ +
    Attribut JavaScript renseigné par le navigateur qui donne la liste des plugins activés (window.navigator.plugins)
    \ +
  • \ +
  • \ +
    La plateforme
    \ +
    Attribut JavaScript renseigné par le navigateur qui indique la plateforme sur laquelle le navigateur fonctionne (window.navigator.platform)
    \ +
  • \ +
  • \ +
    Les préférences pour les cookies (autorisés ou non)
    \ +
    Attribut JavaScript renseigné par le navigateur qui indique si les cookies sont acceptés ou non (window.navigator.cookieEnabled)
    \ +
  • \ +
  • \ +
    Les préférences pour Do Not Track (« Ne pas me pister ») : oui / non / non communiquées
    \ +
    Attribut JavaScript renseigné par le navigateur qui indique la valeur du paramètre Do Not Track (window.navigator.doNotTrack), "NC" indique que la valeur n’a pas été renseignée
    \ +
  • \ +
  • \ +
    Le fuseau horaire
    \ +
    Fuseau horaire du navigateur disponible via JavaScript (new Date().getTimezoneOffset())
    \ +
  • \ +
  • \ +
    La résolution d’écran et le niveau de couleur
    \ +
    Attributs JavaScript renseignés par le navigateur qui indiquent la résolution de l’écran de l’appareil (window.screen.height/width/colorDepth)
    \ +
  • \ +
  • \ +
    L’utilisation du stockage local
    \ +
    Test JavaScript pour savoir si le stockage local de données est supporté par le navigateur (écriture et lecture d’une valeur spécifique dans "localStorage")
    \ +
  • \ +
  • \ +
    L’utilisation du stockage de session
    \ +
    Test JavaScript pour savoir si le stockage de données de session est supporté par la navigateur (écriture et lecture d’une valeur spécifique dans "sessionStorage")
    \ +
  • \ +
  • \ +
    Une image générée avec l’élément HTML Canvas
    \ +
    Rendu d’une image selon une suite d’instructions précises. L’image finale présente des différences selon le système d’exploitation et le navigateur utilisés.
    \ +
  • \ +
  • \ +
    Une image générée avec WebGL
    \ +
    Rendu de trois formes 3D selon une suite d’instructions précises. L’image finale présente des différences selon l’appareil utilisé.
    \ +
  • \ +
  • \ +
    La présence d’AdBlock
    \ +
    Test pour savoir si l’extension AdBlock est installée
    \ +
  • \ +
  • \ +
    La liste des polices
    \ +
    Attribut Flash qui donne la liste complète des polices installées sur le système d’exploitation de l’appareil (flash.text.Font.enumerateFonts(true))
    \ +
  • \ +
\ +

Outre ces données, nous recueillons plusieurs types d’informations « courantes » qui nous aident à analyser les données d’empreintes numériques:\ +

    \ +
  • Les Cookies
  • \ +
  • Les adresses IP chiffrées
  • \ +
  • L’horodatage
  • \ +
\ +

Notre pratique et nos objectifs quand nous collectons ces informations « courantes » sont discutés ci-après. \ +

Cookies

\ +

Le site AmIUnique définit un cookie qui persiste pendant quatre mois dans le but de déterminer à quelle fréquence les caractéristiques du navigateur changent ou restent les mêmes, et ce, à chaque connexion. Si votre navigateur est configuré pour accepter les cookies, et que vous revenez sur AmIUnique plusieurs fois, le cookie sera utilisé pour lier les données de vos visites ensemble de sorte que nous puissions étudier l’évolution « naturelle » des empreintes numériques du navigateur. Si vous souhaitez désactiver / activer les cookies du navigateur, cliquez sur le lien ci-dessous correspondant à votre navigateur pour obtenir des instructions. Vous devez être conscient que les cookies entièrement invalidants peuvent bloquer certaines fonctions interactives sur certains sites Web (plus particulièrement, les connexions automatiques).

\ +

Instructions pour :\ +

\ +En outre, si vous êtes intéressé(e) par l’activation / désactivation des cookies Flash, vous pouvez en savoir plus en visitant la page suivante : \ +Gérer les cookies Flash.\ +

Adresses IP

\ +Le site AmIUnique n’enregistre pas les adresses IP, mais nous soumettons chaque adresse IP à une fonction de hachage. Ce hachage des IP nous permettra de recueillir un ensemble de données anonymes sur la façon dont les navigateurs qui changent d’adresse IP auraient pu être pistés par leur empreinte numérique.\ +

Horodatage

\ +Le site d’AmIUnique recueille un horodatage « flou », arrondi à l’heure près, chaque fois qu’il est visité. Cet horodatage servira à mesurer la vitesse à laquelle les empreintes numériques du navigateur changent, mais à aucune autre fin.\ +

Notre utilisation des informations recueillies sur AmIUnique

\ +Nous allons analyser les données recueillies afin d’établir quels sont les profils réalistes pour les empreintes numériques du navigateur. Le but de cette analyse est de définir une procédure automatique pour diversifier de manière proactive les plateformes d’usagers d’une manière réaliste. AmIUnique n’a pas de tiers fournisseurs de services.\ +

Partage des données d’AmIUnique

\ +Nous pouvons publier ou partager des données statistiques agrégées du projet AmIUnique afin de faciliter la recherche sur la vie privée, d’éduquer les gens aux problèmes de la vie privée, et d’aider au développement des technologies de protection de la confidentialité. Nous avons fait en sorte que AmIUnique ne produise aucun enregistrement des habitudes de navigation de quiconque ni les identités des visiteurs individuels, et nous ne serons donc jamais dans une position où nous pourrions partager ces dossiers.\ +

Sécurité

\ +D’une manière générale, l’INRIA s’engage à mettre en œuvre des moyens techniques et organisationnels afin de protéger toutes les informations que nous avons collectées contre la destruction illégale ou fortuite, la perte fortuite, l’altération, la diffusion ou l’accès non autorisé. Néanmoins, l’INRIA peut être tenu à faire connaître des informations pour se conformer à une loi ou une réglementation applicable, ou pour répondre à une injonction administrative ou judiciaire.\ +

Bien que nous ayons pris toutes les précautions pour stocker les informations recueillies dans un environnement d’exploitation sécurisé, nous ne pouvons pas garantir une sécurité intégrale. L’information recueillie sera maintenue pendant une durée appropriée pour les besoins de ce projet de recherche.
\ +

Cette politique de confidentialité s’inspire pour une part du site web de l’EFF (Electronic Frontier Foundation), qui est librement redistribuée sous une licence\ + Creative Commons Attribution. fp.overview=Vue d’ensemble fp.details=Détails fp.graphs=Graphiques fp.title=Êtes-vous unique ? -fp.track1=Oui ! (Vous pouvez être traçé !) +fp.track1=Oui ! (Vous pouvez être tracé !) fp.track2=Presque ! (Vous pouvez très probablement être tracé.) fp.track3=Non ! (Il est difficile de vous tracer avec l’empreinte de votre navigateur.) @@ -50,7 +174,7 @@ fp.unknownBrow=% des navigateurs observés n’ont pas été identifiés, comme fp.os1=% des navigateurs observés sont installés sur fp.os2=, comme le vôtre. -fp.unknownOs=% of des navigateurs observés sont installés sur un système d’exploitation qui n’a pas été identifié, comme le vôtre. +fp.unknownOs=% des navigateurs observés sont installés sur un système d’exploitation qui n’a pas été identifié, comme le vôtre. fp.lang1=% des navigateurs observés sont réglés en fp.lang2=, comme le vôtre. @@ -58,8 +182,9 @@ fp.lang2=, comme le vôtre. fp.time1=% des navigateurs observés sont réglés sur le fuseau horaire fp.time2=, comme le vôtre. -fp.uniq1=Vous avez le seul navigateur parmi les -fp.uniq2=observés avec cette empreinte. +fp.uniq1=Cependant, la combinaison de tous les éléments constituant votre empreinte est unique parmi les +fp.uniq2=empreintes déjà récoltées. +fp.uniq3=Cliquez ici pour savoir pourquoi. fp.notuniq1=Mais seulement fp.notuniq2=navigateurs parmi les @@ -68,8 +193,11 @@ fp.notuniq4=%) ont exactement la même empreinte que celle de votre navigateur. fp.btn1=Voir plus de détails fp.btn2=Voir graphiques +fp.btnforcefp= Forcer fingerprinting +fp.forcefp=Nous utilisons un cookie temporaire de 3 minutes pour conserver votre fingerprint et charger votre empreinte plus rapidement. Si vous avez effectué des modifications et si vous souhaitez les visualiser, cliquez sur forcer fingerprinting. fp.detailsTitle=Mon empreinte +fp.detProgress=Calcul des ratios en cours... fp.detAtt=Attribut fp.detSim=Ratio de similarité fp.detSimExp=Pourcentage d’empreintes qui partagent la même valeur pour chaque attribut @@ -84,6 +212,7 @@ fp.connection=En-tête "Connection" fp.encoding=En-tête "Content encoding" fp.language=En-tête "Content language" fp.plugins=Liste des plugins +fp.detailPlugins=Détail des plugins fp.platformJ=Plateforme fp.cookies=Utilisation des cookies fp.dnt=Utilisation du Do Not Track @@ -93,6 +222,8 @@ fp.local=Utilisation du stockage local fp.session=Utilisation du stockage de session fp.canvas=Canvas fp.webGl=WebGL +fp.vendorWebGl=WebGL Vendor +fp.rendererWebGl=WebGL Renderer fp.fonts=Liste des polices fp.resolutionF=Résolution d’écran fp.languageF=Langage @@ -114,8 +245,146 @@ fp.localExp=Test JavaScript pour savoir si le stockage local de données est sup fp.sessionExp=Test JavaScript pour savoir si le stockage de données de session est supporté par la navigateur (écriture et lecture d’une valeur spécifique dans "sessionStorage") fp.canvasExp=Rendu d’une image selon une suite d’instructions précises. L’image finale présente des différences selon le système d’exploitation et le navigateur utilisés. fp.webGlExp=Rendu de trois formes 3D selon une suite d’instructions précises. L’image finale présente des différences selon l’appareil utilisé. +fp.vendorWebGlExp=(Nouveau!) Nom du matériel responsable du rendu WebGL. Certains navigateurs donnent le nom complet de la carte graphique utilisée. +fp.rendererWebGlExp=(Nouveau!) Nom du générateur de rendu Web GL. Certains navigateurs donnent le nom complet du driver de la carte graphique. fp.fontsExp=Attribut Flash qui donne la liste complète des polices installées sur le système d’exploitation de l’appareil (flash.text.Font.enumerateFonts(true)) fp.resolutionFExp=Attribut Flash qui indique la résolution de l’écran de l’appareil (flash.system.Capabilities. screenResolutionX/Y). Cette résolution peut être différente de la valeur donnée par JavaScript, notamment en présence d’une configuration multi-écrans. fp.languageFExp=Attribut Flash qui indique la langue principale utilisée dans le navigateur (flash.system.Capabilities.language) fp.platformFExp=Attribut Flash qui indique la plateforme sur laquelle le navigateur fonctionne (flash.system.Capabilities.os). Cette valeur peut être beaucoup plus informative que celle donnée par JavaScript. -fp.adBlockExp= Test pour savoir si l’extension AdBlock est installée \ No newline at end of file +fp.adBlockExp= Test pour savoir si l’extension AdBlock est installée + +history.title=Mon historique +history.warning=Sur cette page, vous pouvez à la fois visualiser et comparer l’ensemble de vos empreintes. Si l’une de vos empreintes n’apparaît pas, cela est dû au fait qu’elle n’avait pas évolué entre deux visites consécutives. Par conséquent, nous ne l’avons pas stockée en base. +history.subtitle1=Empreinte du +history.subtitle2=à +history.subtitle3=h +history.nodiff=Votre empreinte ne présente pas de différence avec l’empreinte précédente. +history.before=Avant +history.after=Après +history.differences=Différences +history.warningmodal=Avertissement +history.warningmessage=Vous devez sélectionner 2 checkboxes au minimum. +history.compare = Comparer +history.close = Fermer +history.btn1 = Historique de mes empreintes +history.btn2 = Comparer mes empreintes + +menu.home=Accueil +menu.myfp=Mon empreinte +menu.globalstats= Statistiques globales +menu.history=Mon historique +menu.faq = FAQ +menu.privpolicy = Politique de confidentialité +menu.privtools = Outils pour limiter le traçage sur Internet +menu.timeline=Ma frise chronologique +menu.links= Liens +menu.about = À propos +menu.github=Voir sur GitHub +menu.new = Nouveau +menu.upd = MAJ + +stats.nbentries = Nombre d’empreintes +stats.zero=Il n’y a eu aucune entrée en base sur la période sélectionnée. Veuillez en choisir une nouvelle. +stats.beg=Date de début +stats.end=Date de fin +stats.btn1= Toutes les empreintes +stats.btn2= Dernier mois +stats.btn3= Dernière semaine +stats.btn4= Intervalle personnalisé +stats.btn5= Envoyer + +modal.title=Vous êtes de retour +modal.body1=Vous pouvez vous rendre sur la page +modal.body2=pour calculer votre nouvelle empreinte, puis sur la page +modal.body3= pour visualiser l’évolution de votre empreinte depuis votre dernière visite. +modal.close=Fermer +modal.history=Calculer votre nouvelle empreinte + +timeline.title = Frise chronologique AmIUnique +timeline.noTimeline = Pour étudier en détail l’évolution des empreintes de navigateur, nous avons développé une extension\ + pour Chrome et Firefox qui enregistre quotidiennement votre empreintre pour détecter le moindre changement. \ + Si vous souhaitez connaître l’évolution de votre propre empreinte ou si vous voulez nous aider à étudier\ + la diversité des empreintes de navigateur, n’hésitez pas à installer l’extension ! +timeline.img1 = L’extension vous notifie quand votre empreinte a changé. +timeline.img2 = Analysez l’évolution de votre empreinte grâce à votre frise chronologique personnalisée ! +timeline.img3 = Accédez directement à votre empreinte courante. +timeline.menu1 = Ma frise chronologique +timeline.menu2 = Mon empreinte courante +timeline.empty = Une deuxième empreinte est nécessaire pour voir la frise chronologique.
\ + Revenez dans quelques jours ou attendez que l’extension vous notifie d’une évolution pour voir tous les changements ! + +timeline.headline = L’évolution de votre empreinte +timeline.text =

Vous retrouverez ici toutes les évolutions de votre empreinte de navigateur depuis l’installation de \ + l’extension.

Explorez la frise chronologique ci-dessous!

+timeline.fp = Empreinte +timeline.current = empreinte courante +timeline.evo1 = Evolution de l’empreinte +timeline.evo2 = à +timeline.lang = fr +timeline.noFlash = Flash non détecté +timeline.attribute = Attribut +timeline.before = Avant +timeline.after = Après +timeline.diff = Différence + +pvtools.desc = Sur cette page, vous trouverez des liens vers des outils permettant d’améliorer le contrôle de votre vie privée sur Internet. Actuellement, en ce qui concerne le fingerprinting, la meilleure solution \ + est de tout simplement bloquer les scripts de traçage. Nous ne pouvons pas recommander l’utilisation de spoofers, car il y a un risque que les scripts de traçage détectent ces techniques \ + relativement facilement et identifient votre mensonge. À cause du nombre assez faible de spoofers, des données très discriminantes comme les polices de caractères ou les \ + plugins peuvent être suffisantes pour vous identifier et vous traquer. +pvtools.rec = Recommandé! + +pvtools.ext = Extensions pour navigateur +pvtools.colext = Extension +pvtools.coldesc = Description +pvtools.colsite = Site +pvtools.ublock = Un bloqueur de publicité et de traqueurs efficace avec un faible impact sur les performances ! +pvtools.ghostery = Protégez votre vie privée en bloquant les traqueurs sur le web et en apprenant qui vous observe ! +pvtools.httpseverywhere1 = Chiffrez le web ! Utilisez automatiquement le protocole sécurisé HTTPS sur tous les sites le supportant. Un projet réalisé par l’EFF (Electronic Frontier Foundation). +pvtools.httpseverywhere2 = Cette extension inclut une option pour vérifier les certificats SSL directement sur +pvtools.httpseverywhere3 = l’EFF SSL Observatory. +pvtools.lightbeam1 = Visualisez en détails les serveurs que vous contactez quand vous surfez sur Internet ! Extension développée par Mozilla. +pvtools.lightbeam2 = Présentation de Lightbeam +pvtools.lightbeam3 = par Gary Kovacs, ancien PDG de Mozilla, lors d’une présentation TED. +pvtools.adblock1 = Bloquez les publicités, les trackers et plus encore ! Nous recommandons l’utilisation de listes additionnelles telles que +pvtools.adblock2 = la liste Fanboy Complete AdBlock. +pvtools.disconnect = Bloquez le traçage effectué par les sites tiers et visualisez qui vous traque ! +pvtools.privacybadger = Bloquez les publicités qui vous espionnent et les traqueurs invisibles ! Un projet de l’EFF (Electronic Frontier Foundation). +pvtools.noscript = Prenez le contrôle de ce qui s’exécute dans votre navigateur en bloquant les scripts indésirables ! +pvtools.selfdest = Supprimez les cookies qui ne sont plus utilisés dès que vous fermez un onglet ! +pvtools.searchengine = Moteur de recherche, navigateur et systèmes d’exploitation +pvtools.webpage = Page officielle +pvtools.ddg1 = "Le moteur de recherche qui ne vous espionne pas." Lancé en 2008, DuckDuckGo se différencie des autres moteurs de recherche en mettant l’accent sur la protection de la vie privée de \ + ses utilisateurs et en n’utilisant pas +pvtools.ddg2 = un système de bulle filtrante de recherche +pvtools.ddg3 =. De plus, DuckDuckGo ne collecte pas et ne partage pas d’informations personnelles. +pvtools.tor1 = Tor et le navigateur Tor +pvtools.tor2 = Le réseau Tor est un groupe de serveurs dont le but est d’améliorer l’anonymat de ses utilisateurs en routant tout leur trafic à travers un réseau dédié. Le navigateur Tor est basé sur \ + Firefox et utilise le réseau Tor. Il a été lourdement modifié afin de limiter le plus possible les techniques de fingerprinting connues actuellement. Pour ce faire, il fait en sorte que l’ensemble de ses \ + utilisateurs partage une empreinte commune, même si, en pratique, la réalité est quelque peu différente. +pvtools.tails = Basé sur le navigateur Tor et son réseau, Tails (The Amnestic Incognito Live System) va encore plus loin en fournissant un environnement totalement identique à l’ensemble de ses \ + utilisateurs. Le système d’exploitation a été lourdement modifié afin de ne laisser aucune trace sur l’ordinateur. Avec le navigateur Tor, ce sont les outils les plus puissants qui existent aujourd’hui \ + contre le fingerprinting au prix d’une diminution du confort d’utilisation. +pvtools.trackOff = TrackOFF, une startup créée à Baltimore en 2015, a lancé une suite logicielle visant à contrer les systèmes de traçage en modifiant l’empreinte de votre navigateur dès qu’une\ + menace est détectée. Disponible pour Chrome et Firefox sous Windows, une période d’essai est disponible pour tester ce nouveau service. +pvtools.ip1 = Adresse IP +pvtools.ip2 = Contrairement à ce que beaucoup de personnes peuvent penser, modifier son adresse IP n’a pas d’impact sur le fingerprinting, car elle ne fait pas partie des informations récoltées \ + pour constituer votre empreinte. Votre ordinateur peut toujours être identifié et traqué sur plusieurs sessions de navigation. En tant que telle, l’adresse IP est une source\ + d’identification très forte, car elle varie très peu dans le temps et dépend de votre fournisseur d’Internet. Malgré tout, modifier son adresse IP est une bonne manière de couvrir ses pistes et de masquer \ + sa position réelle lorsqu’on navigue sur le web. +pvtools.ip3 = La manière la plus répandue de changer son adresse IP est d’utiliser un service de VPN (Virtual Private Network) qui va acheminer l’ensemble de votre trafic réseau à travers des serveurs \ + sécurisés. Le site +pvtools.ip4 = Best VPN +pvtools.ip5 = présente une longue liste de fournisseurs de VPN avec des avis détaillés sur leurs forces et leurs faiblesses. +pvtools.cookies1 = Cookies +pvtools.cookies2 = Les cookies peuvent être utilisés en plus des techniques de fingerprinting pour traquer les internautes et identifier avec facilité les utilisateurs qui reviennent sur un même site. \ + Une manière de contrer cette forme de traçage est de bloquer au maximum les cookies indésirables. Un des réglages recommandés est de ne jamais accepter les cookies provenant de sites tiers, car \ + c’est l’une des manières les plus communes pour les régies publicitaires de vous traquer. Vous trouverez les instructions sur leur blocage pour +pvtools.cookies3 = Firefox +pvtools.cookies4 = et +pvtools.cookies5 = Chrome +pvtools.cookies6 = Pour les internautes les plus soucieux du respect de leur vie privée, vous pouvez complétement bloquer les cookies dans les options du navigateur +pvtools.cookies7 = ou +pvtools.cookies8 = ou utiliser une extension comme Self-Destructing Cookies que nous avons mentionnée dans la section sur les extensions. L’inconvénient majeur d’un blocage total des cookies est qu’il \ + vous faut maintenir une liste à jour des sites autorisés si vous voulez les utiliser sur certains d’entre eux. + + diff --git a/website/conf/routes b/website/conf/routes index ece8566..08c33cc 100644 --- a/website/conf/routes +++ b/website/conf/routes @@ -4,14 +4,27 @@ # Home page GET / controllers.Application.home() -GET /fp controllers.Application.fp() -GET /fpNoJs controllers.Application.fpNoJs() -GET /stats controllers.Application.stats() +GET /fp controllers.FPController.fp() +GET /fpNoJs controllers.FPController.fpNoJs() +GET /viewFP controllers.FPController.viewFP() +GET /stats controllers.StatsController.stats() +GET /history controllers.HistoryController.history() GET /faq controllers.Application.faq() +GET /tools controllers.Application.tools() GET /privacy controllers.Application.privacy() GET /links controllers.Application.links() GET /about controllers.Application.about() -POST /results controllers.Application.addFingerprint() +GET /extension controllers.ExtensionController.extension() +GET /timeline/:id controllers.ExtensionController.timeline(id: String) +GET /timeline controllers.ExtensionController.noTimeline() +GET /getNbEvol/:id controllers.ExtensionController.getNbEvol(id: String) + +POST /results controllers.FPController.addFingerprint() +POST /percentages controllers.StatsController.percentages() +POST /stats controllers.StatsController.statsTime() +POST /evolution/:id controllers.ExtensionController.addFingerprintFromExtension(id: String) +POST /compareFpHistory controllers.HistoryController.compareFpHistory() + #JavaScript router GET /assets/js/routes controllers.Application.jsRoutes() diff --git a/website/project/build.properties b/website/project/build.properties index 3cc3282..f1bfaa9 100644 --- a/website/project/build.properties +++ b/website/project/build.properties @@ -1,4 +1,4 @@ #Activator-generated Properties #Thu Oct 09 13:55:35 CEST 2014 template.uuid=a4c33fda-0fa0-458b-a153-0a28c61df830 -sbt.version=0.13.5 +sbt.version=0.13.9 diff --git a/website/project/plugins.sbt b/website/project/plugins.sbt index f1e4266..f8e5cb9 100644 --- a/website/project/plugins.sbt +++ b/website/project/plugins.sbt @@ -1,7 +1,7 @@ -resolvers += "Typesafe repository" at "http://repo.typesafe.com/typesafe/releases/" +resolvers += "Typesafe repository" at "https://repo.typesafe.com/typesafe/releases/" // The Play plugin -addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.5") +addSbtPlugin("com.typesafe.play" % "sbt-plugin" % "2.3.10") // web plugins diff --git a/website/public/font-awesome-4.2.0/css/font-awesome.min.css b/website/public/font-awesome-4.2.0/css/font-awesome.min.css deleted file mode 100644 index ec53d4d..0000000 --- a/website/public/font-awesome-4.2.0/css/font-awesome.min.css +++ /dev/null @@ -1,4 +0,0 @@ -/*! - * Font Awesome 4.2.0 by @davegandy - http://fontawesome.io - @fontawesome - * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) - */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.2.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.2.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff?v=4.2.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.2.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.2.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"} \ No newline at end of file diff --git a/website/public/font-awesome-4.2.0/fonts/FontAwesome.otf b/website/public/font-awesome-4.2.0/fonts/FontAwesome.otf deleted file mode 100644 index 81c9ad9..0000000 Binary files a/website/public/font-awesome-4.2.0/fonts/FontAwesome.otf and /dev/null differ diff --git a/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.eot b/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.eot deleted file mode 100644 index 84677bc..0000000 Binary files a/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.eot and /dev/null differ diff --git a/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.woff b/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.woff deleted file mode 100644 index 628b6a5..0000000 Binary files a/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.woff and /dev/null differ diff --git a/website/public/font-awesome/css/font-awesome.min.css b/website/public/font-awesome/css/font-awesome.min.css new file mode 100644 index 0000000..ee4e978 --- /dev/null +++ b/website/public/font-awesome/css/font-awesome.min.css @@ -0,0 +1,4 @@ +/*! + * Font Awesome 4.4.0 by @davegandy - http://fontawesome.io - @fontawesome + * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.4.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.4.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.4.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.4.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.4.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.4.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"} diff --git a/website/public/font-awesome/fonts/FontAwesome.otf b/website/public/font-awesome/fonts/FontAwesome.otf new file mode 100644 index 0000000..681bdd4 Binary files /dev/null and b/website/public/font-awesome/fonts/FontAwesome.otf differ diff --git a/website/public/font-awesome/fonts/fontawesome-webfont.eot b/website/public/font-awesome/fonts/fontawesome-webfont.eot new file mode 100644 index 0000000..a30335d Binary files /dev/null and b/website/public/font-awesome/fonts/fontawesome-webfont.eot differ diff --git a/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.svg b/website/public/font-awesome/fonts/fontawesome-webfont.svg similarity index 78% rename from website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.svg rename to website/public/font-awesome/fonts/fontawesome-webfont.svg index d907b25..6fd19ab 100644 --- a/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.svg +++ b/website/public/font-awesome/fonts/fontawesome-webfont.svg @@ -1,6 +1,6 @@ - + @@ -147,14 +147,14 @@ - + - + @@ -275,7 +275,7 @@ - + @@ -399,7 +399,7 @@ - + @@ -411,8 +411,8 @@ - - + + @@ -438,7 +438,7 @@ - + @@ -459,7 +459,7 @@ - + @@ -483,13 +483,13 @@ - + - + @@ -513,8 +513,128 @@ - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.ttf b/website/public/font-awesome/fonts/fontawesome-webfont.ttf similarity index 70% rename from website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.ttf rename to website/public/font-awesome/fonts/fontawesome-webfont.ttf index 96a3639..d7994e1 100644 Binary files a/website/public/font-awesome-4.2.0/fonts/fontawesome-webfont.ttf and b/website/public/font-awesome/fonts/fontawesome-webfont.ttf differ diff --git a/website/public/font-awesome/fonts/fontawesome-webfont.woff b/website/public/font-awesome/fonts/fontawesome-webfont.woff new file mode 100644 index 0000000..6fd4ede Binary files /dev/null and b/website/public/font-awesome/fonts/fontawesome-webfont.woff differ diff --git a/website/public/font-awesome/fonts/fontawesome-webfont.woff2 b/website/public/font-awesome/fonts/fontawesome-webfont.woff2 new file mode 100644 index 0000000..5560193 Binary files /dev/null and b/website/public/font-awesome/fonts/fontawesome-webfont.woff2 differ diff --git a/website/public/images/extension/ami-1.png b/website/public/images/extension/ami-1.png new file mode 100644 index 0000000..4d5d806 Binary files /dev/null and b/website/public/images/extension/ami-1.png differ diff --git a/website/public/images/extension/ami-2.png b/website/public/images/extension/ami-2.png new file mode 100644 index 0000000..73bbff3 Binary files /dev/null and b/website/public/images/extension/ami-2.png differ diff --git a/website/public/images/extension/ami-3.png b/website/public/images/extension/ami-3.png new file mode 100644 index 0000000..0d71df5 Binary files /dev/null and b/website/public/images/extension/ami-3.png differ diff --git a/website/public/images/logo.png b/website/public/images/logo.png new file mode 100644 index 0000000..bcff148 Binary files /dev/null and b/website/public/images/logo.png differ diff --git a/website/public/images/logoSmall.png b/website/public/images/logoSmall.png new file mode 100644 index 0000000..7fb4dc5 Binary files /dev/null and b/website/public/images/logoSmall.png differ diff --git a/website/public/images/tools/adblock.png b/website/public/images/tools/adblock.png new file mode 100644 index 0000000..9977b60 Binary files /dev/null and b/website/public/images/tools/adblock.png differ diff --git a/website/public/images/tools/chrome.png b/website/public/images/tools/chrome.png new file mode 100644 index 0000000..87f31ac Binary files /dev/null and b/website/public/images/tools/chrome.png differ diff --git a/website/public/images/tools/disconnect.jpg b/website/public/images/tools/disconnect.jpg new file mode 100644 index 0000000..d7bdf25 Binary files /dev/null and b/website/public/images/tools/disconnect.jpg differ diff --git a/website/public/images/tools/duck.png b/website/public/images/tools/duck.png new file mode 100644 index 0000000..f8dca04 Binary files /dev/null and b/website/public/images/tools/duck.png differ diff --git a/website/public/images/tools/firefox.png b/website/public/images/tools/firefox.png new file mode 100644 index 0000000..78ae46e Binary files /dev/null and b/website/public/images/tools/firefox.png differ diff --git a/website/public/images/tools/ghostery.png b/website/public/images/tools/ghostery.png new file mode 100644 index 0000000..d697cb7 Binary files /dev/null and b/website/public/images/tools/ghostery.png differ diff --git a/website/public/images/tools/https.jpg b/website/public/images/tools/https.jpg new file mode 100644 index 0000000..de512b8 Binary files /dev/null and b/website/public/images/tools/https.jpg differ diff --git a/website/public/images/tools/lightbeam.png b/website/public/images/tools/lightbeam.png new file mode 100644 index 0000000..c84ae25 Binary files /dev/null and b/website/public/images/tools/lightbeam.png differ diff --git a/website/public/images/tools/noscript.png b/website/public/images/tools/noscript.png new file mode 100644 index 0000000..7707b76 Binary files /dev/null and b/website/public/images/tools/noscript.png differ diff --git a/website/public/images/tools/privacy-badger.png b/website/public/images/tools/privacy-badger.png new file mode 100644 index 0000000..e21b3c4 Binary files /dev/null and b/website/public/images/tools/privacy-badger.png differ diff --git a/website/public/images/tools/self.png b/website/public/images/tools/self.png new file mode 100644 index 0000000..5bb9a17 Binary files /dev/null and b/website/public/images/tools/self.png differ diff --git a/website/public/images/tools/tails.png b/website/public/images/tools/tails.png new file mode 100644 index 0000000..24089a2 Binary files /dev/null and b/website/public/images/tools/tails.png differ diff --git a/website/public/images/tools/tor.png b/website/public/images/tools/tor.png new file mode 100644 index 0000000..ab4f419 Binary files /dev/null and b/website/public/images/tools/tor.png differ diff --git a/website/public/images/tools/trackoff.png b/website/public/images/tools/trackoff.png new file mode 100644 index 0000000..aacc5b2 Binary files /dev/null and b/website/public/images/tools/trackoff.png differ diff --git a/website/public/images/tools/ublock.png b/website/public/images/tools/ublock.png new file mode 100644 index 0000000..14beb5b Binary files /dev/null and b/website/public/images/tools/ublock.png differ diff --git a/website/public/javascripts/IECSSFix.js b/website/public/javascripts/IECSSFix.js new file mode 100644 index 0000000..a584077 --- /dev/null +++ b/website/public/javascripts/IECSSFix.js @@ -0,0 +1,8 @@ +var head = document.getElementsByTagName('head')[0], +style = document.createElement('style'); +style.type = 'text/css'; +style.styleSheet.cssText = ':before,:after{content:none !important}'; +head.appendChild(style); +setTimeout(function(){ + head.removeChild(style); +}, 0); \ No newline at end of file diff --git a/website/public/javascripts/PluginDetect_All.js b/website/public/javascripts/PluginDetect_All.js index c48d43b..503f43d 100644 --- a/website/public/javascripts/PluginDetect_All.js +++ b/website/public/javascripts/PluginDetect_All.js @@ -1,8 +1,8 @@ /* -PluginDetect v0.8.8 +PluginDetect v0.8.9 www.pinlady.net/PluginDetect/license/ [ QuickTime Java DevalVR Flash Shockwave WindowsMediaPlayer Silverlight VLC AdobeReader PDFReader RealPlayer IEcomponent ActiveX PDFjs ] [ isMinVersion getVersion hasMimeType onDetectionDone onWindowLoaded onBeforeInstantiate ] -[ BetterIE ] +[ AllowActiveX BetterIE ] */ -(function(){var a={version:"0.8.8",name:"PluginDetect",addPlugin:function(b,c){b&&a.isString(b)&&c&&a.isFunc(c.getVersion)&&(b=b.replace(/\s/g,"").toLowerCase(),a.Plugins[b]=c,a.isDefined(c.getVersionDone)||(c.installed=null,c.version=null,c.version0=null,c.getVersionDone=null,c.pluginName=b))},openTag:"<",hasOwnProperty:{}.constructor.prototype.hasOwnProperty,hasOwn:function(b,c){var d;try{d=a.hasOwnProperty.call(b,c)}catch(e){}return!!d},rgx:{str:/string/i,num:/number/i,fun:/function/i,arr:/array/i},toString:{}.constructor.prototype.toString,isDefined:function(a){return"undefined"!=typeof a},isArray:function(b){return a.rgx.arr.test(a.toString.call(b))},isString:function(b){return a.rgx.str.test(a.toString.call(b))},isNum:function(b){return a.rgx.num.test(a.toString.call(b))},isStrNum:function(b){return a.isString(b)&&/\d/.test(b)},isFunc:function(b){return a.rgx.fun.test(a.toString.call(b))},getNumRegx:/[\d][\d\.\_,\-]*/,splitNumRegx:/[\.\_,\-]/g,getNum:function(b,c){var d=a.isStrNum(b)?(a.isDefined(c)?RegExp(c):a.getNumRegx).exec(b):null;return d?d[0]:null},compareNums:function(b,c,d){var e=parseInt;if(a.isStrNum(b)&&a.isStrNum(c)){if(a.isDefined(d)&&d.compareNums)return d.compareNums(b,c);b=b.split(a.splitNumRegx);c=c.split(a.splitNumRegx);for(d=0;de(c[d],10))return 1;if(e(b[d],10)d;d++)if(/^(0+)(.+)$/.test(e[d])&&(e[d]=RegExp.$2),d>c||!/\d/.test(e[d]))e[d]="0";return e.slice(0,4).join(",")},pd:{getPROP:function(b,c,d){try{b&&(d=b[c])}catch(e){}return d},findNavPlugin:function(b){if(b.dbug)return b.dbug;if(window.navigator){var c={Find:a.isString(b.find)?RegExp(b.find,"i"):b.find,Find2:a.isString(b.find2)?RegExp(b.find2,"i"):b.find2,Avoid:b.avoid?a.isString(b.avoid)?RegExp(b.avoid,"i"):b.avoid:0,Num:b.num?/\d/:0},d,e,f,g=navigator.mimeTypes,h=navigator.plugins,l=null;if(b.mimes&&g)for(f=a.isArray(b.mimes)?[].concat(b.mimes):a.isString(b.mimes)?[b.mimes]:[],d=0;dh&&"0"!=e[g]||f[g]!=e[g]&&(-1==h&&(h=g),"0"!=e[g]))return c;return d},AXO:function(){var a;try{a=new window.ActiveXObject}catch(c){}return a?null:window.ActiveXObject}(),getAXO:function(b){var c=null;try{c=new a.AXO(b)}catch(d){}c&&(a.browser.ActiveXEnabled=!0);return c},betterIE:function(){var b=a.browser,c=document;b.verIEtrue=null;b.docModeIE=null;if(b.isIE){var d,e=c.createElement("div"),f=["{45EA75A0-A269-11D1-B5BF-0000F8051515}","{3AF36230-A269-11D1-B5BF-0000F8051515}","{89820200-ECBD-11CF-8B85-00AA005B4383}"];try{e.style.behavior="url(#default#clientcaps)"}catch(g){}for(d=0;df.status)return f.status;f=f.plugin;c=a.formatNum(a.isNum(c)?c.toString():a.isStrNum(c)?a.getNum(c):"0");1!=f.getVersionDone&&(f.getVersion(c,d,e),null===f.getVersionDone&&(f.getVersionDone=1));null!==f.installed&&(g=0.5>=f.installed?f.installed:0.7==f.installed?1:null===f.version?0:0<=a.compareNums(f.version,c,f)?1:-0.1);return g},getVersion:function(b,c,d){var e=a.pd.findPlugin(b);if(0>e.status)return null;e=e.plugin;1!=e.getVersionDone&&(e.getVersion(null,c,d),null===e.getVersionDone&&(e.getVersionDone=1));e=(e=e.version||e.version0)?e.replace(a.splitNumRegx,a.pd.getVersionDelimiter):e;return e},hasMimeType:function(b){if(b&&window.navigator&&navigator.mimeTypes){var c,d,e,f=navigator.mimeTypes,g=a.isArray(b)?[].concat(b):a.isString(b)?[b]:[];e=g.length;for(d=0;d=this.len;b--)c[b]&&c[b].span&&this.pluginMayBeHanging(c[b].span)&&(this.emptyNode(c[b].span),c[b].span=null,d=1);this.len=c.length;if(d){try{window.CollectGarbage()}catch(e){}}}},HTML:[],len:0,onDone:function(a,c){var d,e=c.HTML;for(d=0;d'+a.openTag+"/object>";for(c=0;c=a.compareNums(c,b.min))return 1;if(b.max&&0<=a.compareNums(c,b.max))return 0;e.BIHndlrs&&e.BIHndlrs.length&&(a.ev.callArray(e.BIHndlrs));f.innerHTML=b.tagA+c+b.tagB;a.pd.getPROP(f.firstChild,"object")&&(d=1);d?(b.min=c,this.HTML.push({span:f})):(b.max=c,f.innerHTML="");return d},convert_:function(b,c,d,e){return(b=b.convert[c])?a.isFunc(b)?a.formatNum(b(d.split(a.splitNumRegx),e).join(",")):d:b},convert:function(b,c,d){var e,f,g;c=a.formatNum(c);f={v:c,x:-1};if(c)for(e=0;ea.compareNums(c,d?this.convert_(b,e,b.Upper[e]):b.Upper[e]))){f.v=this.convert_(b,e,c,d);f.x=e;break}return f},isMin:function(b,c){var d,e=0;if(!a.isStrNum(c)||this.isDisabled())return e;this.init(b);if(!b.L)for(b.L={},d=0;d=a.compareNums(c,b.L.v))?1:-1));return e},search:function(b){var c=this,d=b.$$,e=0,f;f=b.searchHasRun||c.isDisabled()?1:0;b.searchHasRun=1;if(f)return b.version||null;c.init(b);var g,h=function(d,f){var g=[].concat(m);g[d]=f;g=c.isActiveXObject(b,g.join(","));g?(e=1,m[d]=f):k[d]=f;return g},l=b.DIGITMAX,n,p,m=[0,0,0,0],k=[0,0,0,0];for(f=0;fk[f]&&0<=a.compareNums(p,b.Lower[g])&&0>a.compareNums(n,b.Upper[g])&&(k[f]=Math.floor(l[g][f])));for(g=0;30>g;g++){if(16>=k[f]-m[f]){for(g=k[f];g>=m[f]+(f?1:0)&&!h(f,g);g--);break}h(f,Math.round((k[f]+m[f])/2))}if(!e)break;k[f]=m[f];}e&&(b.version=c.convert(b,m.join(",")).v);return b.version||null}},win:{loaded:!1,hasRun:0,init:function(){this.hasRun||(this.hasRun=1,this.addEvent("load",this.onLoad),this.addEvent("unload",this.onUnload))},addEvent:function(b,c){var d=window;a.isFunc(c)&&(d.addEventListener?d.addEventListener(b,c,!1):d.attachEvent?d.attachEvent("on"+b,c):d["on"+b]=this.concatFn(c,d["on"+b]))},concatFn:function(a,c){return function(){a();"function"==typeof c&&c()}},loadPrvtHndlrs:[],loadPblcHndlrs:[],unloadHndlrs:[],onUnload:function(){if(a&&a.win){a.ev.callArray(a.win.unloadHndlrs);for(var b in a)a[b]=0;a=0}},count:0,countMax:1,intervalLength:10,onLoad:function(){if(a&&a.win&&!a.win.loaded){var b=a.win;b.counth||0>m||c<=this.pluginSize)return 0;g&&!b.pi&&a.isDefined(n)&&a.browser.isIE&&b.tagName==k.tagName&&b.time<=k.time&&h===m&&0===l&&0!==d&&(b.pi=1);if(m=c&&(!b.winLoaded&&a.win.loaded||a.isNum(f)&&(a.isNum(b.count2)||(b.count2=f),0.'+a.openTag+"/div>"),d=e.getElementById("pd33993399")}catch(g){}if(f=e.getElementsByTagName("body")[0]||e.body)f.insertBefore(b,f.firstChild),d&&f.removeChild(d)},insert:function(b,c,d,e,f,g,h){var l,k=document.createElement("span");a.isDefined(e)||(e="");if(a.isString(b)&&/[^\s]/.test(b)){b=b.toLowerCase().replace(/\s/g,"");l=a.openTag+b+" ";l+='style="'+this.getStyle.plugin(g)+'" ';var p=1,m=1;for(g=0;g');l+=e+a.openTag+"/"+b+">"}else b="",l=e;h||this.initDiv();c=h||this.getDiv();b={span:null,winLoaded:a.win.loaded,tagName:b,outerHTML:l,DOM:this,time:(new Date).getTime(),width:this.width,obj:this.obj,readyState:this.readyState,objectProperty:this.objectProperty};if(c&&c.parentNode){f&&f.BIHndlrs&&f.BIHndlrs.length&&(a.ev.callArray(f.BIHndlrs));this.setStyle(k,this.getStyle.span());c.appendChild(k);try{k.innerHTML=l}catch(q){}b.span=k;b.winLoaded=a.win.loaded}return b}},file:{any:"fileStorageAny999",valid:"fileStorageValid999",save:function(b,c,d){b&&a.isDefined(d)&&(b[this.any]||(b[this.any]=[]),b[this.valid]||(b[this.valid]=[]),b[this.any].push(d),(c=this.split(c,d))&&b[this.valid].push(c))},getValidLength:function(a){return a&&a[this.valid]?a[this.valid].length:0},getAnyLength:function(a){return a&&a[this.any]?a[this.any].length:0},getValid:function(a,c){return a&&a[this.valid]?this.get(a[this.valid],c):null},getAny:function(a,c){return a&&a[this.any]?this.get(a[this.any],c):null},get:function(b,c){var d=b.length-1,e=a.isNum(c)?c:d;return 0>e||e>d?null:b[e]},split:function(b,c){var d=null,e;b=b?b.replace(".","\\."):"";e=RegExp("^(.*[^\\/])("+b+"\\s*)$");a.isString(c)&&e.test(c)&&(e=RegExp.$1.split("/"),d={name:e[e.length-1],ext:RegExp.$2,full:c},e[e.length-1]="",d.path=e.join("/"));return d}},Plugins:{}};window[a.name]=a;a.INIT();var w={setPluginStatus:function(b,c,d){this.installed=c?1:d?0this.OTF&&(this.installed=g?0this.OTF?0:this.applet.can_Insert_Query_Any()?0:1);a.codebase.emptyGarbage();},DTK:{hasRun:0,status:null,VERSIONS:[],version:"",HTML:null,Plugin2Status:null,classID:["clsid:CAFEEFAC-DEC7-0000-0001-ABCDEFFEDCBA","clsid:CAFEEFAC-DEC7-0000-0000-ABCDEFFEDCBA"],mimeType:["application/java-deployment-toolkit","application/npruntime-scriptable-plugin;DeploymentToolkit"],isDisabled:function(b){return this.HTML?1:b||a.dbug?0:this.hasRun||!a.DOM.isEnabled.objectTagUsingActiveX()?1:0},query:function(b){var c=k,d,e,f=a.DOM.altHTML,g,h=null,l=null;b=this.isDisabled(b);this.hasRun=1;if(b)return this;this.status=0;if(a.DOM.isEnabled.objectTagUsingActiveX())for(d=0;da.compareNums(d,a.formatNum("1,3"))||0<=a.compareNums(d,a.formatNum("2")))&&(d=0);return d},query:function(){var b=k,c=0,b=this.hasRun||!b.navigator.mimeObj;this.hasRun=1;if(b)return this;(!c||a.dbug)&&(b=this.getPlatformNum())&&(c=b);(!c||a.dbug)&&(b=this.getPluginNum())&&(c=b);c&&(this.version=a.formatNum(c));return this}},applet:{codebase:{isMin:function(b){this.$$=k;return a.codebase.isMin(this,b)},search:function(){this.$$=k;return a.codebase.search(this)},DIGITMAX:[[15,128],[6,0,512],0,[1,5,2,256],0,[1,4,1,1],[1,4,0,64],[1,3,2,32]],DIGITMIN:[1,0,0,0],Upper:"999 10 5,0,20 1,5,0,20 1,4,1,20 1,4,1,2 1,4,1 1,4".split(" "),Lower:"10 5,0,20 1,5,0,20 1,4,1,20 1,4,1,2 1,4,1 1,4 0".split(" "),convert:[function(a,c){return c?[1c-1&&a.isNum(b[c-1])&&(0>b[c-1]&&(b[c-1]=0),3a.compareNums(c.verOpera,"13,0,0,0")&&!b.javaEnabled()||this.AppletTag()&&!a.DOM.isEnabled.objectTag()||!b.mimeObj&&!c.isIE?1:0,this.all_=b);return this.all_},AppletTag:function(){var b=k.navigator;return a.browser.isIE?!b.javaEnabled():0},VerifyTagsDefault_1:function(){var b=a.browser;return b.isIE&&(9>b.verIE||!b.ActiveXEnabled)||b.verGecko&&0>a.compareNums(b.verGecko,a.formatNum("2"))||b.isSafari&&(!b.verSafari||0>a.compareNums(b.verSafari,a.formatNum("4")))||b.isOpera&&0>a.compareNums(b.verOpera,a.formatNum("11"))?0:1}},can_Insert_Query:function(a){var c=this.results[0][0],d=this.getResult()[0];return this.HTML[a]||0==a&&null!==c&&!this.isRange(c)||0==a&&d&&!this.isRange(d)?0:!this.isDisabled.single(a)},can_Insert_Query_Any:function(){var a;for(a=0;ac.OTF)){c=h.obj();g=h.readyState();try{d=a.getNum(c.getVersion()+""),e=c.getVendor()+"",c.statusbar(" ")}catch(l){}d&&a.isStrNum(d)&&(f[b]=[d,e],this.active[b]=2);}},isRange:function(a){return/^[<>]/.test(a||"")?">"==a.charAt(0)?1:-1:0},setRange:function(b,c){return(b?0":"<":"")+(a.isString(c)?c:"")},insertJavaTag:function(b,c,d,e,f){var g=k,h=a.file.getValid(g),l=h.path,h=["archive",h.name+h.ext,"code","A.class"];e=(e?["width",e]:[]).concat(f?["height",f]:[]);f=["mayscript","true"];var n=["scriptable","true","codebase_lookup","false"].concat(f),p=g.navigator,p=!a.browser.isIE&&p.mimeObj&&p.mimeObj.type?p.mimeObj.type:g.mimeType[0];if(1==b)return a.DOM.insert("object",["type",p].concat(e),["codebase",l].concat(h).concat(n),d,g,0,c);if(2==b)return a.browser.isIE?a.DOM.insert("applet",["alt",d].concat(f).concat(h).concat(e),["codebase",l].concat(n),d,g,0,c):a.DOM.insert("applet",["codebase",l,"alt",d].concat(f).concat(h).concat(e),[].concat(n),d,g,0,c);if(3==b)return a.browser.isIE?a.DOM.insert("object",["classid",g.classID].concat(e),["codebase",l].concat(h).concat(n),d,g,0,c):a.DOM.insert();if(4==b)return a.DOM.insert("embed",["codebase",l].concat(h).concat(["type",p]).concat(n).concat(e),[],d,g,0,c)},insert_Query_Any:function(b){var c=k,d=this.results,e=this.HTML,f=a.DOM.altHTML,g,h=a.file.getValid(c);this.should_Insert_Query(0)&&(2>c.OTF&&(c.OTF=2),d[0]=[0,0],(g=b?this.codebase.isMin(b):this.codebase.search())&&(d[0][0]=b?this.setRange(g,b):g),this.active[0]=g?1.5:-1);if(!h)return this.getResult();this.DummySpanTagHTML||(this.DummySpanTagHTML=a.DOM.insert("",[],[],f));this.should_Insert_Query(1)&&(2>c.OTF&&(c.OTF=2),e[1]=this.insertJavaTag(1,0,f),d[1]=[0,0],this.query(1));this.should_Insert_Query(2)&&(2>c.OTF&&(c.OTF=2),e[2]=this.insertJavaTag(2,0,f),d[2]=[0,0],this.query(2));this.should_Insert_Query(3)&&(2>c.OTF&&(c.OTF=2),e[3]=this.insertJavaTag(3,0,f),d[3]=[0,0],this.query(3));a.DOM.isEnabled.objectTag()&&(this.DummyObjTagHTML||!e[1]&&!e[2]||(this.DummyObjTagHTML=a.DOM.insert("object",["type",c.mimeType_dummy],[],f)),!this.DummyObjTagHTML2&&e[3]&&(this.DummyObjTagHTML2=a.DOM.insert("object",["classid",c.classID_dummy],[],f)));c.NOTF.init();return this.getResult()}},NOTF:{count:0,countMax:25,intervalLength:250,init:function(){var b=k;3>b.OTF&&this.shouldContinueQuery()&&(b.OTF=3,this.onIntervalQuery=a.ev.handler(this.$$onIntervalQuery,this),a.win.loaded||a.win.loadPrvtHndlrs.push([this.onWinLoadQuery,this]),setTimeout(this.onIntervalQuery,this.intervalLength))},shouldContinueQuery:function(){var b=k.applet,c,d=0;if(a.win.loaded&&this.count>this.countMax)return 0;for(c=0;cthis.countMax&&a.codebase.pluginMayBeHanging(b.HTML[c].span)&&(d=1,b.HTML[c].DELETE=1),!d&&!b.results[c][0]&&(2<=b.allowed[c]||1==b.allowed[c]&&!b.getResult()[0])&&0<=this.isAppletActive(c)))return 1;return 0},isJavaActive:function(a){var c=k,d,e,f=-9;for(d=0;df&&(f=e);return f},isAppletActive:function(b,c){var d=k,e=d.navigator,f=d.applet,g=f.HTML[b],h=f.active,l=0,n;n=h[b];if(c||1.5<=n||!g||!g.span)return n;n=a.DOM.getTagStatus(g,f.DummySpanTagHTML,f.DummyObjTagHTML,f.DummyObjTagHTML2,this.count);for(f=0;fc;c++)if(d=a.getAXO(this.progID+(c?"."+c:""))){this.installed=1;b=0;try{b=a.getNum(d.GetVariable("$version")+"")}catch(e){}if(b&&(this.version=b,!a.dbug))break}return this}},instance:{hasRun:0,version:null,HTML:null,isEnabled:function(){var b=t,c=1;if(this.hasRun||a.DOM.isEnabled.objectTagUsingActiveX()||!a.hasMimeType(b.mimeType))c=0;return c},query:function(){var b=t,c=this.isEnabled();this.hasRun=1;if(c){this.HTML=a.DOM.insert("object",["type",b.mimeType],["play","false","menu","false"],"",b);try{this.version=a.getNum(this.HTML.obj().GetVariable("$version")+"")}catch(d){}}return this}}};a.addPlugin("flash",t);a.addPlugin("shockwave",{getVersion:function(){var b=null,c;c&&!a.dbug||!this.nav.query().installed||(c=1);b&&!a.dbug||!this.nav.query().version||(b=this.nav.version);c&&!a.dbug||!this.axo.query().installed||(c=1);b&&!a.dbug||!this.axo.query().version||(b=this.axo.version);this.installed=b?1:c?0:-1;this.version=a.formatNum(b)},nav:{hasRun:0,installed:0,version:null,mimeType:"application/x-director",query:function(){var b,c;c=this.hasRun||!a.hasMimeType(this.mimeType);this.hasRun=1;if(c)return this;(c=a.pd.findNavPlugin({find:"Shockwave\\s*for\\s*Director",mimes:this.mimeType,plugins:"Shockwave for Director"}))&&c.description&&(b=a.getNum(c.description+""));b&&(b=a.getPluginFileVersion(c,b));c&&(this.installed=1);b&&(this.version=b);return this}},axo:{hasRun:0,installed:null,version:null,progID:"SWCtl.SWCtl SWCtl.SWCtl.1 SWCtl.SWCtl.7 SWCtl.SWCtl.8 SWCtl.SWCtl.11 SWCtl.SWCtl.12".split(" "),classID:"clsid:166B1BCA-3F9C-11CF-8075-444553540000",query:function(){var b,c,d,e;c=!this.hasRun;this.hasRun=1;if(c){for(c=0;ca.compareNums(c.verGecko,a.formatNum("1.8"))||c.isOpera&&0>a.compareNums(c.verOpera,a.formatNum("10"))||a.DOM.isEnabled.objectTagUsingActiveX()||!a.hasMimeType(b.mimeType)||!a.pd.findNavPlugin({find:b.find[0],avoid:b.avoid,mimes:b.mimeType,plugins:b.plugins[0]})?1:0},query:function(){var b=u,c=this.isDisabled();this.hasRun=1;if(c)return this;if(b=a.pd.getPROP(a.DOM.insert("object",["type",a.hasMimeType(b.mimeType).type,"data",""],["src",""],"",b).obj(),"versionInfo"))this.version=a.getNum(b);return this}},axo:{hasRun:0,installed:null,version:null,progID:["WMPlayer.OCX","WMPlayer.OCX.7"],classID:"clsid:6BF52A52-394A-11D3-B153-00C04F79FAA6",query:function(){var b,c;c=!this.hasRun;this.hasRun=1;if(c){for(c=0;cparseInt(b[0],10)&&30226<=parseInt(b[2],10)&&(b[0]="2"),b=b.join(","));b&&(this.version=b);return this}},axo:{hasRun:0,installed:0,version:null,progID:"AgControl.AgControl",maxdigit:[20,10,10,100,100,10],mindigit:[0,0,0,0,0,0],IsVersionSupported:function(b,c){try{return this.testVersion?0<=a.compareNums(a.formatNum(this.testVersion.join(",")),a.formatNum(c.join(","))):b.IsVersionSupported(this.format(c))}catch(d){}return 0},format:function(a){return a[0]+"."+a[1]+"."+a[2]+this.make2digits(a[3])+this.make2digits(a[4])+"."+a[5]},make2digits:function(a){return(10>a?"0":"")+a+""},query:function(){var b,c;b=this.hasRun;this.hasRun=1;if(b)return this;if(c=a.getAXO(this.progID))this.installed=1;if(c&&this.IsVersionSupported(c,this.mindigit)){var d=[].concat(this.mindigit),e,f=0;for(b=0;be;)e++,f++,d[b]=Math.round((this.maxdigit[b]+this.mindigit[b])/2),this.IsVersionSupported(c,d)?this.mindigit[b]=d[b]:this.maxdigit[b]=d[b];d[b]=this.mindigit[b]}this.version=this.format(d);}return this}}});var x={compareNums:function(b,c){var d=b.split(a.splitNumRegx),e=c.split(a.splitNumRegx),f,g,h,k,n;for(f=0;fh?1:-1;if(2==f&&k!=n)return k>n?1:-1}return 0},setPluginStatus:function(b,c,d){this.installed=c?1:d?0e;e++)if(b.test(c)){if(f=a.formatNum(RegExp.$1),!d||0d))d=f}else break;return d},axo:{detected:0,version:null,progID:["AcroPDF.PDF","AcroPDF.PDF.1","PDF.PdfCtrl","PDF.PdfCtrl.5","PDF.PdfCtrl.1"],query:function(){var b=y,c,d=null,e;if(this.detected)return this;this.detected=-1;for(e=0;ethis.doc.isDisabled()?0:1);},getVersion:function(b,c,d){b=!1;var e=this.doc,f=this.verify;a.isDefined(d)&&(this.detectIE3P=d?1:0);null===this.getVersionDone&&(this.OTF=0,f&&f.init());a.file.save(this,".pdf",c);0===this.getVersionDone?f&&f.isEnabled()&&a.isNum(this.installed)&&0<=this.installed||(e.insertHTMLQuery(),this.setPluginStatus()):((!b||a.dbug)&&0this.result&&b.detectIE3P||a.dbug)for(c=0;c=a.compareNums(d.verGecko,a.formatNum("10"))&&4>=a.OS||d.isOpera&&0>=a.compareNums(d.verOpera,a.formatNum("11"))&&4>=a.OS||d.isChrome&&0>a.compareNums(d.verChrome,a.formatNum("10"))&&4>=a.OS)a.dbug||(c=2);2>c&&((b=a.file.getValid(b))&&b.full||(c=1));return c},queryObject:function(b){var c=r,d=0;d=a.DOM.getTagStatus(this.HTML,this.DummySpanTagHTML,this.DummyObjTagHTML1,0,b,1);return this.result=d},insertHTMLQuery:function(){var b=r,c,d=a.DOM.altHTML;if(this.isDisabled())return this.result;2>b.OTF&&(b.OTF=2);c=a.file.getValid(b).full;this.DummySpanTagHTML||(this.DummySpanTagHTML=a.DOM.insert("",[],[],d,b,1));this.HTML||(this.HTML=a.DOM.insert("object",(a.browser.isIE&&!b.detectIE3P?["classid",this.classID]:["type",this.mimeType]).concat(["data",c]),["src",c],d,b,1));this.DummyObjTagHTML1||(this.DummyObjTagHTML1=a.DOM.insert("object",a.browser.isIE&&!b.detectIE3P?["classid",this.classID_dummy]:["type",this.mimeType_dummy],[],d,b,1));this.queryObject();a.browser.isIE&&0===this.result&&(this.HTML.span.innerHTML=this.HTML.outerHTML,this.DummyObjTagHTML1.span.innerHTML=this.DummyObjTagHTML1.outerHTML,this.queryObject());if((0this.result)&&!a.dbug)return this.result;b.NOTF.init();return this.result}},NOTF:{count:0,countMax:25,intervalLength:250,init:function(){var b=r,c=b.doc;3>b.OTF&&c.HTML&&(b.OTF=3,this.onIntervalQuery=a.ev.handler(this.$$onIntervalQuery,this),a.win.loaded||a.win.loadPrvtHndlrs.push([this.onWinLoadQuery,this]),setTimeout(this.onIntervalQuery,this.intervalLength))},$$onIntervalQuery:function(b){var c=r,d=c.doc;3==c.OTF&&(d.queryObject(b.count),(d.result||a.win.loaded&&b.count>b.countMax)&&b.queryCompleted());b.count++;3==c.OTF&&setTimeout(b.onIntervalQuery,b.intervalLength)},onWinLoadQuery:function(a,c){var d=r,e=d.doc;3==d.OTF&&(e.queryObject(c.count),c.queryCompleted())},queryCompleted:function(){var b=r;4!=b.OTF&&(b.OTF=4,b.setPluginStatus(),a.onDetectionDone&&b.DoneHndlrs&&(a.ev.callArray(b.DoneHndlrs)))}},z:0};a.addPlugin("pdfreader",r);var v={mimeType:["audio/x-pn-realaudio-plugin","audio/x-pn-realaudio"],classID:"clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA",setPluginStatus:function(b,c){c&&(this.version=a.formatNum(a.getNum(c)));this.installed=this.version?1:b?0:-1;this.getVersionDone=-1==this.installed||this.axo.version||this.instance.version?1:0;},navObj:{hasRun:0,installed:null,version:null,find:"RealPlayer.*Plug-?in",avoid:"Totem|QuickTime|Helix|VLC|Download",plugins:["RealPlayer(tm) G2 LiveConnect-Enabled Plug-In (32-bit) ","RealPlayer(tm) G2 LiveConnect-Enabled Plug-In (64-bit) ","RealPlayer Plugin"],query:function(){var b=v,c=!this.hasRun&&a.hasMimeType(b.mimeType);this.hasRun=1;c&&(this.installed=(b=a.pd.findNavPlugin({find:this.find,avoid:this.avoid,mimes:b.mimeType,plugins:this.plugins}))?1:0,(b=a.getPluginFileVersion(b))&&0<=a.compareNums(a.formatNum(b),a.formatNum("15"))&&(this.version=b));return this}},JS:{hasRun:0,version:null,regStr:"RealPlayer.*Version.*Plug-?in",mimetype:"application/vnd.rn-realplayer-javascript",q1:[[11,0,0],[999],[663],[663],[663],[660],[468],[468],[468],[468],[468],[468],[431],[431],[431],[372],[180],[180],[172],[172],[167],[114],[0]],q3:[[6,0],[12,99],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,46],[12,46],[12,46],[11,3006],[11,2806],[11,2806],[11,2804],[11,2804],[11,2799],[11,2749],[11,2700]],compare:function(a,c){var d,e=a.length,f=c.length,g,h;for(d=0;dh)return 1;if(gc[0].length?e.slice(c[0].length):[];if(0this.compare(e,c[c.length-1]))return null;for(b=c.length-1;1<=b&&1!=b&&(0!=this.compare(c[b],e)||0!=this.compare(c[b],c[b-1]))&&!(0<=this.compare(e,c[b])&&0>this.compare(e,c[b-1]));b--);return d[0].join(".")+"."+d[b].join(".")},isEnabled:function(){return!this.hasRun&&1==a.OS&&a.hasMimeType(this.mimetype)?1:0},query:function(){var b,c;c=this.isEnabled();this.hasRun=1;if(c){(c=a.pd.findNavPlugin({find:this.regStr,mimes:this.mimetype}))&&(b=a.formatNum(a.getNum(c.description)));if(b){var d=b.split(a.splitNumRegx);c=1;0>this.compare(d,[6,0,12,200])?c=-1:0>=this.compare(d,[6,0,12,1739])&&0<=this.compare(d,[6,0,12,857])&&(c=-1);0>c&&(this.version=(c=this.convertNum(b,this.q3,this.q1))?c:b)}}return this}},instance:{hasRun:0,version:null,HTML:null,isEnabled:function(){var b=v,c=1;!a.dbug&&(this.hasRun||a.DOM.isEnabled.objectTagUsingActiveX()||!a.hasMimeType(b.mimeType)||a.browser.isGecko&&0>a.compareNums(a.browser.verGecko,a.formatNum("1,8"))||a.browser.isOpera&&0>a.compareNums(a.browser.verOpera,a.formatNum("10")))&&(c=0);return c},query:function(){var b=v,c=this.isEnabled();this.hasRun=1;if(c){this.HTML=a.DOM.insert("object",["type",b.mimeType[0]],"src autostart false imagestatus false controls stopbutton".split(" "),"",b);b=this.HTML.obj();try{this.version=a.getNum(b.GetVersionInfo())}catch(d){}a.DOM.setStyle(b,["display","none"]);}return this}},axo:{hasRun:0,installed:null,version:null,progID:["rmocx.RealPlayer G2 Control","rmocx.RealPlayer G2 Control.1","RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)","RealVideo.RealVideo(tm) ActiveX Control (32-bit)","RealPlayer"],query:function(){var b,c,d;if(!this.hasRun){this.hasRun=1;for(c=0;cd[e]||99999999(ethis.doc.isDisabled()?0:1);},getVersion:function(b,c){var d=this.verify,e=this.doc;null===this.getVersionDone&&(this.OTF=0,d&&d.init());a.file.save(this,".pdf",c);0===this.getVersionDone&&d&&d.isEnabled()&&a.isNum(this.installed)&&0<=this.installed||(e.insertHTMLQuery(),this.setPluginStatus())},doc:{result:0,mimeType:"application/pdf",mimeType_dummy:"application/dummymimepdf",DummySpanTagHTML:0,HTML:0,DummyObjTagHTML1:0,isDisabled:function(){var b=s,c=0,d=a.browser;if(2<=b.OTF)c=2;else if(!a.DOM.isEnabled.objectTag()||!d.isGecko||0>a.compareNums(d.verGecko,a.formatNum("10"))||0>a.compareNums(d.verGecko,a.formatNum("19"))&&a.hasMimeType(this.mimeType))a.dbug||(c=2);2>c&&((b=a.file.getValid(b))&&b.full||(c=1));return c},tabIndex:null,method:"",queryObject:function(b){var c=this.HTML?this.HTML.obj():0,d,e=a.dbug&&!a.win.loaded?0:1;b=a.DOM.getTagStatus(this.HTML,this.DummySpanTagHTML,this.DummyObjTagHTML1,0,b);(!this.result||a.dbug)&&0>b&&(e&&(this.result=-1),this.method+="1,");(!this.result||a.dbug)&&0b.OTF&&(b.OTF=2);c=a.file.getValid(b).full;this.DummySpanTagHTML||(this.DummySpanTagHTML=a.DOM.insert("",[],[],d,b,1));this.HTML||(this.HTML=a.DOM.insert("object",["type",this.mimeType,"data",c],["src",c],d,b,1));this.DummyObjTagHTML1||(this.DummyObjTagHTML1=a.DOM.insert("object",["type",this.mimeType_dummy],[],d,b,1));this.queryObject();if((0this.result)&&!a.dbug)return this.result;b.NOTF.init();return this.result}},NOTF:{count:0,countMax:25,intervalLength:250,init:function(){var b=s,c=b.doc;3>b.OTF&&c.HTML&&(b.OTF=3,this.onIntervalQuery=a.ev.handler(this.$$onIntervalQuery,this),a.win.loaded||a.win.loadPrvtHndlrs.push([this.onWinLoadQuery,this]),setTimeout(this.onIntervalQuery,this.intervalLength))},$$onIntervalQuery:function(b){var c=s,d=c.doc;3==c.OTF&&(d.queryObject(b.count),(d.result||a.win.loaded&&b.count>b.countMax)&&b.queryCompleted());b.count++;3==c.OTF&&setTimeout(b.onIntervalQuery,b.intervalLength)},onWinLoadQuery:function(a,c){var d=s,e=d.doc;3==d.OTF&&(e.queryObject(c.count),c.queryCompleted())},queryCompleted:function(){var b=s;4!=b.OTF&&(b.OTF=4,b.setPluginStatus(),a.onDetectionDone&&b.DoneHndlrs&&(a.ev.callArray(b.DoneHndlrs)))}},z:0};a.addPlugin("pdfjs",s);})(); \ No newline at end of file +(function(){var j={version:"0.8.9",name:"PluginDetect",addPlugin:function(p,q){if(p&&j.isString(p)&&q&&j.isFunc(q.getVersion)){p=p.replace(/\s/g,"").toLowerCase();j.Plugins[p]=q;if(!j.isDefined(q.getVersionDone)){q.installed=null;q.version=null;q.version0=null;q.getVersionDone=null;q.pluginName=p;}}},openTag:"<",hasOwnPROP:({}).constructor.prototype.hasOwnProperty,hasOwn:function(s,t){var p;try{p=j.hasOwnPROP.call(s,t)}catch(q){}return !!p},rgx:{str:/string/i,num:/number/i,fun:/function/i,arr:/array/i},toString:({}).constructor.prototype.toString,isDefined:function(p){return typeof p!="undefined"},isArray:function(p){return j.rgx.arr.test(j.toString.call(p))},isString:function(p){return j.rgx.str.test(j.toString.call(p))},isNum:function(p){return j.rgx.num.test(j.toString.call(p))},isStrNum:function(p){return j.isString(p)&&(/\d/).test(p)},isFunc:function(p){return j.rgx.fun.test(j.toString.call(p))},getNumRegx:/[\d][\d\.\_,\-]*/,splitNumRegx:/[\.\_,\-]/g,getNum:function(q,r){var p=j.isStrNum(q)?(j.isDefined(r)?new RegExp(r):j.getNumRegx).exec(q):null;return p?p[0]:null},compareNums:function(w,u,t){var s,r,q,v=parseInt;if(j.isStrNum(w)&&j.isStrNum(u)){if(j.isDefined(t)&&t.compareNums){return t.compareNums(w,u)}s=w.split(j.splitNumRegx);r=u.split(j.splitNumRegx);for(q=0;qv(r[q],10)){return 1}if(v(s[q],10)r||!(/\d/).test(s[p])){s[p]="0"}}return s.slice(0,4).join(",")},pd:{getPROP:function(s,q,p){try{if(s){p=s[q]}}catch(r){}return p},findNavPlugin:function(u){if(u.dbug){return u.dbug}var A=null;if(window.navigator){var z={Find:j.isString(u.find)?new RegExp(u.find,"i"):u.find,Find2:j.isString(u.find2)?new RegExp(u.find2,"i"):u.find2,Avoid:u.avoid?(j.isString(u.avoid)?new RegExp(u.avoid,"i"):u.avoid):0,Num:u.num?/\d/:0},s,r,t,y,x,q,p=navigator.mimeTypes,w=navigator.plugins;if(u.mimes&&p){y=j.isArray(u.mimes)?[].concat(u.mimes):(j.isString(u.mimes)?[u.mimes]:[]);for(s=0;s-1&&p>r&&s[p]!="0"){return q}if(v[p]!=s[p]){if(r==-1){r=p}if(s[p]!="0"){return q}}}return t},AXO:(function(){var q;try{q=new window.ActiveXObject()}catch(p){}return q?null:window.ActiveXObject})(),getAXO:function(p){var r=null;try{r=new j.AXO(p)}catch(q){j.errObj=q;}if(r){j.browser.ActiveXEnabled=!0}return r},browser:{detectPlatform:function(){var r=this,q,p=window.navigator?navigator.platform||"":"";j.OS=100;if(p){var s=["Win",1,"Mac",2,"Linux",3,"FreeBSD",4,"iPhone",21.1,"iPod",21.2,"iPad",21.3,"Win.*CE",22.1,"Win.*Mobile",22.2,"Pocket\\s*PC",22.3,"",100];for(q=s.length-2;q>=0;q=q-2){if(s[q]&&new RegExp(s[q],"i").test(p)){j.OS=s[q+1];break}}}},detectIE:function(){var r=this,u=document,t,q,v=window.navigator?navigator.userAgent||"":"",w,p,y;r.ActiveXFilteringEnabled=!1;r.ActiveXEnabled=!1;try{r.ActiveXFilteringEnabled=!!window.external.msActiveXFilteringEnabled()}catch(s){}p=["Msxml2.XMLHTTP","Msxml2.DOMDocument","Microsoft.XMLDOM","TDCCtl.TDCCtl","Shell.UIHelper","HtmlDlgSafeHelper.HtmlDlgSafeHelper","Scripting.Dictionary"];y=["WMPlayer.OCX","ShockwaveFlash.ShockwaveFlash","AgControl.AgControl"];w=p.concat(y);for(t=0;t=7?u.documentMode:0)||((/^(?:.*?[^a-zA-Z])??(?:MSIE|rv\s*\:)\s*(\d+\.?\d*)/i).test(v)?parseFloat(RegExp.$1,10):7)}r.betterIE();},betterIE:function(){var r=this,v=document;r.verIEtrue=null;r.docModeIE=null;if(r.isIE){var s,u=v.createElement("div"),q,p=["{45EA75A0-A269-11D1-B5BF-0000F8051515}","{3AF36230-A269-11D1-B5BF-0000F8051515}","{89820200-ECBD-11CF-8B85-00AA005B4383}"];try{u.style.behavior="url(#default#clientcaps)"}catch(t){}for(q=0;q0&&j.isFunc(q[0])))){p.push(q)}},call0:function(q){var p=j.isArray(q)?q.length:-1;if(p>0&&j.isFunc(q[0])){q[0](j,p>1?q[1]:0,p>2?q[2]:0,p>3?q[3]:0)}else{if(j.isFunc(q)){q(j)}}},callArray0:function(p){var q=this,r;if(j.isArray(p)){while(p.length){r=p[0];p.splice(0,1);if(j.win&&j.win.unload&&p!==j.win.unloadHndlrs){}else{q.call0(r)}}}},call:function(q){var p=this;p.call0(q);p.ifDetectDoneCallHndlrs()},callArray:function(p){var q=this;q.callArray0(p);q.ifDetectDoneCallHndlrs()},allDoneHndlrs:[],ifDetectDoneCallHndlrs:function(){var r=this,p,q;if(!r.allDoneHndlrs.length){return}if(j.win){if(!j.win.loaded||j.win.loadPrvtHndlrs.length||j.win.loadPblcHndlrs.length){return}}if(j.Plugins){for(p in j.Plugins){if(j.hasOwn(j.Plugins,p)){q=j.Plugins[p];if(q&&j.isFunc(q.getVersion)){if(q.OTF==3||(q.DoneHndlrs&&q.DoneHndlrs.length)||(q.BIHndlrs&&q.BIHndlrs.length)){return}}}}}r.callArray0(r.allDoneHndlrs);}},isMinVersion:function(v,u,r,q){var s=j.pd.findPlugin(v),t,p=-1;if(s.status<0){return s.status}t=s.plugin;u=j.formatNum(j.isNum(u)?u.toString():(j.isStrNum(u)?j.getNum(u):"0"));if(t.getVersionDone!=1){t.getVersion(u,r,q);if(t.getVersionDone===null){t.getVersionDone=1}}if(t.installed!==null){p=t.installed<=0.5?t.installed:(t.installed==0.7?1:(t.version===null?0:(j.compareNums(t.version,u,t)>=0?1:-0.1)))}return p},getVersion:function(u,r,q){var s=j.pd.findPlugin(u),t,p;if(s.status<0){return null}t=s.plugin;if(t.getVersionDone!=1){t.getVersion(null,r,q);if(t.getVersionDone===null){t.getVersionDone=1}}p=(t.version||t.version0);p=p?p.replace(j.splitNumRegx,j.pd.getVersionDelimiter):p;return p},hasMimeType:function(t){if(t&&window.navigator&&navigator.mimeTypes){var w,v,q,s,p=navigator.mimeTypes,r=j.isArray(t)?[].concat(t):(j.isString(t)?[t]:[]);s=r.length;for(q=0;q=r.len;p--){if(t[p]&&t[p].span&&r.pluginMayBeHanging(t[p].span)){r.emptyNode(t[p].span);t[p].span=null;q=1}}r.len=t.length;if(q){try{window.CollectGarbage()}catch(s){}}},isMin:function(u,t){var s=this,r,q,p=0;if(!j.isStrNum(t)||s.isDisabled()){return p}s.init(u);if(!u.L){u.L={};for(r=0;r=0){p=(u.L.x==q.x?s.isActiveXObject(u,q.v):j.compareNums(t,u.L.v)<=0)?1:-1}}return p},search:function(v){var B=this,w=v.$$,q=0,r;r=v.searchHasRun||B.isDisabled()?1:0;v.searchHasRun=1;if(r){return v.version||null}B.init(v);var F,E,D,s=v.DIGITMAX,t,p,C=99999999,u=[0,0,0,0],G=[0,0,0,0];var A=function(y,J){var H=[].concat(u),I;H[y]=J;I=B.isActiveXObject(v,H.join(","));if(I){q=1;u[y]=J}else{G[y]=J}return I};for(F=0;FG[F]&&j.compareNums(p,v.Lower[D])>=0&&j.compareNums(t,v.Upper[D])<0){G[F]=Math.floor(s[D][F])}}}for(E=0;E<30;E++){if(G[F]-u[F]<=16){for(D=G[F];D>=u[F]+(F?1:0);D--){if(A(F,D)){break}}break}A(F,Math.round((G[F]+u[F])/2))}if(!q){break}G[F]=u[F];}if(q){v.version=B.convert(v,u.join(",")).v}return v.version||null},emptyNode:function(p){try{p.innerHTML=""}catch(q){}},HTML:[],len:0,onUnload:function(r,q){var p,s=q.HTML;for(p=0;p'+j.openTag+"/object>";for(p=0;p=0){return 0}if(s.BIHndlrs&&s.BIHndlrs.length){j.ev.callArray(s.BIHndlrs);}r.innerHTML=u.tagA+q+u.tagB;if(j.pd.getPROP(r.firstChild,"object")){p=1}if(p){u.min=q;t.HTML.push({span:r})}else{u.max=q;r.innerHTML=""}return p},convert_:function(t,p,q,s){var r=t.convert[p];return r?(j.isFunc(r)?j.formatNum(r(q.split(j.splitNumRegx),s).join(",")):q):r},convert:function(v,r,u){var t=this,q,p,s;r=j.formatNum(r);p={v:r,x:-1};if(r){for(q=0;q=0&&(!q||j.compareNums(r,u?t.convert_(v,q,v.Upper[q]):v.Upper[q])<0)){p.v=t.convert_(v,q,r,u);p.x=q;break}}}return p},z:0},win:{disable:function(){this.cancel=true},cancel:false,loaded:false,unload:false,hasRun:0,init:function(){var p=this;if(!p.hasRun){p.hasRun=1;if((/complete/i).test(document.readyState||"")){p.loaded=true;}else{p.addEvent("load",p.onLoad)}p.addEvent("unload",p.onUnload)}},addEvent:function(r,q){var s=this,p=window;if(p.addEventListener){p.addEventListener(r,q,false)}else{if(p.attachEvent){p.attachEvent("on"+r,q)}else{p["on"+r]=s.concatFn(q,p["on"+r])}}},removeEvent:function(r,q){var p=window;if(p.removeEventListener){p.removeEventListener(r,q,false)}else{if(p.detachEvent){p.detachEvent("on"+r,q)}}},concatFn:function(q,p){return function(){q();if(typeof p=="function"){p()}}},loadPrvtHndlrs:[],loadPblcHndlrs:[],unloadHndlrs:[],onUnload:function(){var p=j.win;if(p.unload){return}p.unload=true;p.removeEvent("load",p.onLoad);p.removeEvent("unload",p.onUnload);j.ev.callArray(p.unloadHndlrs)},count:0,countMax:1,intervalLength:10,onLoad:function(){var p=j.win;if(p.loaded||p.unload||p.cancel){return}if(p.count=0;q--){v=w[q];if(v){t.emptyNode(v.span);t.removeNode(v.span);v.span=null}w[q]=0}}r=t.getDiv();t.emptyNode(r);t.removeNode(r);s=0;r=0;t.div=0},width:function(){var t=this,s=t.span,q,r,p=-1;q=s&&j.isNum(s.scrollWidth)?s.scrollWidth:p;r=s&&j.isNum(s.offsetWidth)?s.offsetWidth:p;s=0;return r>0?r:(q>0?q:Math.max(r,q))},obj:function(){return this.span?this.span.firstChild||null:null},readyState:function(){var p=this;return j.browser.isIE&&j.isDefined(j.pd.getPROP(p.span,"readyState"))?j.pd.getPROP(p.obj(),"readyState"):j.UNDEFINED},objectProperty:function(){var r=this,q=r.DOM,p;if(q.isEnabled.objectProperty(r)){p=j.pd.getPROP(r.obj(),"object")}return p},getTagStatus:function(q,A,F,E,t,D,v){var G=this;if(!q||!q.span){return -2}var y=q.width(),r=q.obj()?1:0,s=q.readyState(),p=q.objectProperty();if(p){return 1.5}var u=/clsid\s*\:/i,C=F&&u.test(F.outerHTML||"")?F:(E&&u.test(E.outerHTML||"")?E:0),w=F&&!u.test(F.outerHTML||"")?F:(E&&!u.test(E.outerHTML||"")?E:0),z=q&&u.test(q.outerHTML||"")?C:w;if(!A||!A.span||!z||!z.span){return 0}var x=z.width(),B=A.width(),H=z.readyState();if(y<0||x<0||B<=G.pluginSize){return 0}if(v&&!q.pi&&j.isDefined(p)&&j.browser.isIE&&q.tagName==z.tagName&&q.time<=z.time&&y===x&&s===0&&H!==0){q.pi=1}if(x0){return q.pi?-0.5:-1}}}else{if(y==G.pluginSize&&r&&(!j.isNum(s)||s===4)){if(!q.winLoaded&&D){return 1}if(j.isNum(t)){if(!j.isNum(q.count)){q.count=t}if(q.winLoaded&&t-q.count>4){return 1}}}else{if(!q.winLoaded&&D){return q.pi?-0.5:-1}if(j.isNum(t)){if(!j.isNum(q.count3)){q.count3=t}if(q.winLoaded&&q.count3-t>6){return q.pi?-0.5:-1}}}}return q.pi?-0.1:0},setStyle:function(q,t){var s=q.style,p;if(s&&t){for(p=0;p.'+j.openTag+"/div>");q=s.getElementById(u)}catch(r){}}p=s.getElementsByTagName("body")[0]||s.body;if(p){p.insertBefore(v,p.firstChild);if(q){p.removeChild(q)}}v=0},insert:function(u,t,v,p,y,w,B){var E=this,A=document,G,F,D=A.createElement("span"),s,C;if(!j.isDefined(p)){p=""}if(j.isString(u)&&(/[^\s]/).test(u)){u=u.toLowerCase().replace(/\s/g,"");G=j.openTag+u+" ";G+='style="'+E.getStyle.plugin(w)+'" ';var r=1,q=1;for(C=0;C"}else{G+=">";for(C=0;C'}}G+=p+j.openTag+"/"+u+">"}}else{u="";G=p}if(!B){E.initDiv()}s=B||E.getDiv();F={span:null,winLoaded:j.win.loaded,tagName:u,outerHTML:G,DOM:E,time:new Date().getTime(),width:E.width,obj:E.obj,readyState:E.readyState,objectProperty:E.objectProperty};if(s&&s.parentNode){if(y&&y.BIHndlrs&&y.BIHndlrs.length){j.ev.callArray(y.BIHndlrs);}E.setStyle(D,E.getStyle.span());s.appendChild(D);try{D.innerHTML=G}catch(z){}F.span=D;F.winLoaded=j.win.loaded}D=0;s=0;B=0;E.HTML.push(F);return F}},file:{any:"fileStorageAny999",valid:"fileStorageValid999",save:function(s,t,r){var q=this,p;if(s&&j.isDefined(r)){if(!s[q.any]){s[q.any]=[]}if(!s[q.valid]){s[q.valid]=[]}s[q.any].push(r);p=q.split(t,r);if(p){s[q.valid].push(p)}}},getValidLength:function(p){return p&&p[this.valid]?p[this.valid].length:0},getAnyLength:function(p){return p&&p[this.any]?p[this.any].length:0},getValid:function(r,p){var q=this;return r&&r[q.valid]?q.get(r[q.valid],p):null},getAny:function(r,p){var q=this;return r&&r[q.any]?q.get(r[q.any],p):null},get:function(s,p){var r=s.length-1,q=j.isNum(p)?p:r;return(q<0||q>r)?null:s[q]},split:function(t,q){var s=null,p,r;t=t?t.replace(".","\\."):"";r=new RegExp("^(.*[^\\/])("+t+"\\s*)$");if(j.isString(q)&&r.test(q)){p=(RegExp.$1).split("/");s={name:p[p.length-1],ext:RegExp.$2,full:q};p[p.length-1]="";s.path=p.join("/")}return s}},Plugins:{}};j.init.library();var i={setPluginStatus:function(q,p,s){var r=this;r.version=p?j.formatNum(p,3):null;r.installed=r.version?1:(s?(s>0?0.7:-0.1):(q?0:-1));r.getVersionDone=r.installed==0.7||r.installed==-0.1||r.nav.done===0?0:1;j.codebase.emptyGarbage();},getVersion:function(s,t){var u=this,p=null,r=0,q;t=j.browser.isIE?0:t;if((!r||j.dbug)&&u.nav.query(t).installed){r=1}if((!p||j.dbug)&&u.nav.query(t).version){p=u.nav.version}q=!p?u.codebase.isMin(s):0;if(q){u.setPluginStatus(0,0,q);return}if(!p||j.dbug){q=u.codebase.search();if(q){r=1;p=q}}if((!r||j.dbug)&&u.axo.query().installed){r=1}if((!p||j.dbug)&&u.axo.query().version){p=u.axo.version}u.setPluginStatus(r,p)},nav:{done:null,installed:0,version:null,result:[0,0],mimeType:["video/quicktime","application/x-quicktimeplayer","image/x-macpaint","image/x-quicktime","application/x-rtsp","application/x-sdp","application/sdp","audio/vnd.qcelp","video/sd-video","audio/mpeg","video/mp4","video/3gpp2","application/x-mpeg","audio/x-m4b","audio/x-aac","video/flc"],find:"QuickTime.*Plug-?in",find2:"QuickTime.*Plug-?in",find3filename:"QuickTime|QT",avoid:"Totem|VLC|RealPlayer|Helix|MPlayer|Windows\\s*Media\\s*Player",plugins:"QuickTime Plug-in",detect:function(s){var t=this,r,q,p={installed:0,version:null,plugin:null};r=j.pd.findNavPlugin({find:t.find,find2:s?0:t.find2,avoid:s?0:t.avoid,mimes:t.mimeType,plugins:t.plugins});if(r){p.plugin=r;p.installed=1;q=new RegExp(t.find,"i");if(r.name&&q.test(r.name+"")){p.version=j.getNum(r.name+"")}}return p},query:function(r){var q=this,t,s;r=r?1:0;if(q.done===null){if(j.hasMimeType(q.mimeType)){s=q.detect(1);if(s.installed){t=q.detect(0);q.result=[t,t.installed?t:s]}var x=q.result[0],v=q.result[1],w=new RegExp(q.avoid,"i"),u=new RegExp(q.find3filename,"i"),p;x=x?x.plugin:0;v=v?v.plugin:0;if(!x&&v&&v.name&&(!v.description||(/^[\s]*$/).test(v.description+""))&&!w.test(v.name+"")){p=(v.filename||"")+"";if((/^.*[\\\/]([^\\\/]*)$/).test(p)){p=RegExp.$1;}if(p&&u.test(p)&&!w.test(p)){q.result[0]=q.result[1]}}}q.done=q.result[0]===q.result[1]?1:0;}if(q.result[r]){q.installed=q.result[r].installed;q.version=q.result[r].version}return q}},codebase:{classID:"clsid:02BF25D5-8C17-4B23-BC80-D3488ABDDC6B",isMin:function(r){var s=this,q,p=0;s.$$=i;if(j.isStrNum(r)){q=r.split(j.splitNumRegx);if(q.length>3&&parseInt(q[3],10)>0){q[3]="9999"}r=q.join(",");p=j.codebase.isMin(s,r)}return p},search:function(){this.$$=i;return j.codebase.search(this)},DIGITMAX:[[12,11,11],[7,60],[7,11,11],0,[7,11,11]],DIGITMIN:[5,0,0,0],Upper:["999","7,60","7,50","7,6","7,5"],Lower:["7,60","7,50","7,6","7,5","0"],convert:[1,function(r,q){return q?[r[0],r[1]+r[2],r[3],"0"]:[r[0],r[1].charAt(0),r[1].charAt(1),r[2]]},1,0,1]},axo:{hasRun:0,installed:0,version:null,progID:["QuickTimeCheckObject.QuickTimeCheck","QuickTimeCheckObject.QuickTimeCheck.1"],progID0:"QuickTime.QuickTime",query:function(){var r=this,t,p,q,s=r.hasRun||!j.browser.ActiveXEnabled;r.hasRun=1;if(s){return r}for(p=0;p0?0.7:-0.1):(v?1:(p?-0.2:-1))}if(t.OTF==2&&t.NOTF&&!t.applet.getResult()[0]){t.installed=p?-0.2:-1}if(t.OTF==3&&t.installed!=-0.5&&t.installed!=0.5){t.installed=(t.NOTF.isJavaActive(1)>=1?0.5:-0.5)}if(t.OTF==4&&(t.installed==-0.5||t.installed==0.5)){if(v){t.installed=1}else{if(q){t.installed=q>0?0.7:-0.1}else{if(t.NOTF.isJavaActive(1)>=1){if(p){t.installed=1;v=p}else{t.installed=0}}else{if(p){t.installed=-0.2}else{t.installed=-1}}}}}if(p){t.version0=j.formatNum(j.getNum(p))}if(v&&!q){t.version=j.formatNum(j.getNum(v))}if(w&&j.isString(w)){t.vendor=w}if(!t.vendor){t.vendor=""}if(t.verify&&t.verify.isEnabled()){t.getVersionDone=0}else{if(t.getVersionDone!=1){if(t.OTF<2){t.getVersionDone=0}else{t.getVersionDone=t.applet.can_Insert_Query_Any()?0:1}}}j.codebase.emptyGarbage();},DTK:{hasRun:0,status:null,VERSIONS:[],version:"",HTML:null,Plugin2Status:null,classID:["clsid:CAFEEFAC-DEC7-0000-0001-ABCDEFFEDCBA","clsid:CAFEEFAC-DEC7-0000-0000-ABCDEFFEDCBA"],mimeType:["application/java-deployment-toolkit","application/npruntime-scriptable-plugin;DeploymentToolkit"],isDisabled:function(p){var q=this;if(q.HTML){return 1}if(p||j.dbug){return 0}if(q.hasRun||!j.DOM.isEnabled.objectTagUsingActiveX()){return 1}return 0},query:function(B){var z=this,t=a,A,v,p=j.DOM.altHTML,u={},q,s=null,w=null,r=z.isDisabled(B);z.hasRun=1;if(r){return z}z.status=0;if(j.DOM.isEnabled.objectTagUsingActiveX()){for(A=0;A0?1:-1;for(A=0;A0){p.version=q;p.mimeObj=s;p.pluginObj=r;p.mimetype=s.type;}}},query:function(){var t=this,s=a,w,v,B,A,z,r,q=navigator.mimeTypes,p=t.isDisabled();t.hasRun=1;if(p){return t}r=q.length;if(j.isNum(r)){for(w=0;w=5){p="1,"+RegExp.$1+","+(RegExp.$2?RegExp.$2:"0")+","+(RegExp.$3?RegExp.$3:"0");}return p},getPluginNum:function(){var s=this,q=a,p=0,u,t,r,w,v=0;r=/Java[^\d]*Plug-in/i;w=j.pd.findNavPlugin({find:r,num:1,mimes:q.mimeType,plugins:1,dbug:v});if(w){u=s.checkPluginNum(w.description,r);t=s.checkPluginNum(w.name,r);p=u&&t?(j.compareNums(u,t)>0?u:t):(u||t)}if(!p){r=/Java.*\d.*Plug-in/i;w=j.pd.findNavPlugin({find:r,mimes:q.mimeType,plugins:1,dbug:v});if(w){u=s.checkPluginNum(w.description,r);t=s.checkPluginNum(w.name,r);p=u&&t?(j.compareNums(u,t)>0?u:t):(u||t)}}return p},checkPluginNum:function(s,r){var p,q;p=r.test(s)?j.formatNum(j.getNum(s)):0;if(p&&j.compareNums(p,j.formatNum("10"))>=0){q=p.split(j.splitNumRegx);p=j.formatNum("1,"+(parseInt(q[0],10)-3)+",0,"+q[1])}if(p&&(j.compareNums(p,j.formatNum("1,3"))<0||j.compareNums(p,j.formatNum("2"))>=0)){p=0}return p},query:function(){var t=this,s=a,r,p=0,q=t.hasRun||!s.navigator.mimeObj;t.hasRun=1;if(q){return t}if(!p||j.dbug){r=t.getPlatformNum();if(r){p=r}}if(!p||j.dbug){r=t.getPluginNum();if(r){p=r}}if(p){t.version=j.formatNum(p)}return t}},applet:{codebase:{isMin:function(p){this.$$=a;return j.codebase.isMin(this,p)},search:function(){this.$$=a;return j.codebase.search(this)},DIGITMAX:[[15,128],[6,0,512],0,[1,5,2,256],0,[1,4,1,1],[1,4,0,64],[1,3,2,32]],DIGITMIN:[1,0,0,0],Upper:["999","10","5,0,20","1,5,0,20","1,4,1,20","1,4,1,2","1,4,1","1,4"],Lower:["10","5,0,20","1,5,0,20","1,4,1,20","1,4,1,2","1,4,1","1,4","0"],convert:[function(r,q){return q?[parseInt(r[0],10)>1?"99":parseInt(r[1],10)+3+"",r[3],"0","0"]:["1",parseInt(r[0],10)-3+"","0",r[1]]},function(r,q){return q?[r[1],r[2],r[3]+"0","0"]:["1",r[0],r[1],r[2].substring(0,r[2].length-1||1)]},0,function(r,q){return q?[r[0],r[1],r[2],r[3]+"0"]:[r[0],r[1],r[2],r[3].substring(0,r[3].length-1||1)]},0,1,function(r,q){return q?[r[0],r[1],r[2],r[3]+"0"]:[r[0],r[1],r[2],r[3].substring(0,r[3].length-1||1)]},1]},results:[[null,null],[null,null],[null,null],[null,null]],getResult:function(){var q=this,s=q.results,p,r=[];for(p=s.length-1;p>=0;p--){r=s[p];if(r[0]){break}}r=[].concat(r);return r},DummySpanTagHTML:0,HTML:[0,0,0,0],active:[0,0,0,0],DummyObjTagHTML:0,DummyObjTagHTML2:0,allowed:[1,1,1,1],VerifyTagsHas:function(q){var r=this,p;for(p=0;pp-1&&j.isNum(r[p-1])){if(r[p-1]<0){r[p-1]=0}if(r[p-1]>3){r[p-1]=3}q.allowed[p]=r[p-1]}}q.allowed[0]=q.allowed[3];}},setVerifyTagsArray:function(r){var q=this,p=a;if(p.getVersionDone===null){q.saveAsVerifyTagsArray(p.getVerifyTagsDefault())}if(j.dbug){q.saveAsVerifyTagsArray([3,3,3])}else{if(r){q.saveAsVerifyTagsArray(r)}}},isDisabled:{single:function(q){var p=this;if(p.all()){return 1}if(q==1){return !j.DOM.isEnabled.objectTag()}if(q==2){return p.AppletTag()}if(q===0){return j.codebase.isDisabled()}if(q==3){return !j.DOM.isEnabled.objectTagUsingActiveX()}return 1},all_:null,all:function(){var r=this,t=a,q=t.navigator,p,s=j.browser;if(r.all_===null){if((s.isOpera&&j.compareNums(s.verOpera,"13,0,0,0")<0&&!q.javaEnabled())||(r.AppletTag()&&!j.DOM.isEnabled.objectTag())||(!q.mimeObj&&!s.isIE)){p=1}else{p=0}r.all_=p}return r.all_},AppletTag:function(){var q=a,p=q.navigator;return j.browser.isIE?!p.javaEnabled():0},VerifyTagsDefault_1:function(){var q=j.browser,p=1;if(q.isIE&&!q.ActiveXEnabled){p=0}if((q.isIE&&q.verIE<9)||(q.verGecko&&j.compareNums(q.verGecko,j.formatNum("2"))<0)||(q.isSafari&&(!q.verSafari||j.compareNums(q.verSafari,j.formatNum("4"))<0))||(q.isOpera&&j.compareNums(q.verOpera,j.formatNum("11"))<0)){p=0}return p}},can_Insert_Query:function(s){var q=this,r=q.results[0][0],p=q.getResult()[0];if(q.HTML[s]||(s===0&&r!==null&&!q.isRange(r))||(s===0&&p&&!q.isRange(p))){return 0}return !q.isDisabled.single(s)},can_Insert_Query_Any:function(){var q=this,p;for(p=0;p0||!r.isRange(p));if(!r.can_Insert_Query(s)||t[s]===0){return 0}if(t[s]==3||(t[s]==2.8&&!p)){return 1}if(!q.nonAppletDetectionOk(q.version0)){if(t[s]==2||(t[s]==1&&!p)){return 1}}return 0},should_Insert_Query_Any:function(){var q=this,p;for(p=0;p]/).test(p||"")?(p.charAt(0)==">"?1:-1):0},setRange:function(q,p){return(q?(q>0?">":"<"):"")+(j.isString(p)?p:"")},insertJavaTag:function(y,z,p,s,D){var t=a,v="A.class",A=j.file.getValid(t),x=A.name+A.ext,w=A.path;var u=["archive",x,"code",v],E=(s?["width",s]:[]).concat(D?["height",D]:[]),r=["mayscript","true"],C=["scriptable","true","codebase_lookup","false"].concat(r),B=t.navigator,q=!j.browser.isIE&&B.mimeObj&&B.mimeObj.type?B.mimeObj.type:t.mimeType[0];if(y==1){return j.browser.isIE?j.DOM.insert("object",["type",q].concat(E),["codebase",w].concat(u).concat(C),p,t,0,z):j.DOM.insert("object",["type",q].concat(E),["codebase",w].concat(u).concat(C),p,t,0,z)}if(y==2){return j.browser.isIE?j.DOM.insert("applet",["alt",p].concat(r).concat(u).concat(E),["codebase",w].concat(C),p,t,0,z):j.DOM.insert("applet",["codebase",w,"alt",p].concat(r).concat(u).concat(E),[].concat(C),p,t,0,z)}if(y==3){return j.browser.isIE?j.DOM.insert("object",["classid",t.classID].concat(E),["codebase",w].concat(u).concat(C),p,t,0,z):j.DOM.insert()}if(y==4){return j.DOM.insert("embed",["codebase",w].concat(u).concat(["type",q]).concat(C).concat(E),[],p,t,0,z)}return j.DOM.insert()},insert_Query_Any:function(u){var t=this,s=a,v=t.results,w=t.HTML,r=j.DOM.altHTML,q,p=j.file.getValid(s);if(t.should_Insert_Query(0)){if(s.OTF<2){s.OTF=2}v[0]=[0,0];q=u?t.codebase.isMin(u):t.codebase.search();if(q){v[0][0]=u?t.setRange(q,u):q}t.active[0]=q?1.5:-1}if(!p){return t.getResult()}if(!t.DummySpanTagHTML){t.DummySpanTagHTML=j.DOM.insert("",[],[],r)}if(t.should_Insert_Query(1)){if(s.OTF<2){s.OTF=2}w[1]=t.insertJavaTag(1,0,r);v[1]=[0,0];t.query(1)}if(t.should_Insert_Query(2)){if(s.OTF<2){s.OTF=2}w[2]=t.insertJavaTag(2,0,r);v[2]=[0,0];t.query(2)}if(t.should_Insert_Query(3)){if(s.OTF<2){s.OTF=2}w[3]=t.insertJavaTag(3,0,r);v[3]=[0,0];t.query(3)}if(j.DOM.isEnabled.objectTag()){if(!t.DummyObjTagHTML&&(w[1]||w[2])){t.DummyObjTagHTML=j.DOM.insert("object",["type",s.mimeType_dummy],[],r)}if(!t.DummyObjTagHTML2&&w[3]){t.DummyObjTagHTML2=j.DOM.insert("object",["classid",s.classID_dummy],[],r)}}s.NOTF.init();return t.getResult()}},NOTF:{winLoaded:0,count:0,countMax:25,intervalLength:250,init:function(){var q=this,p=a;if(p.OTF<3&&q.shouldContinueQuery()){p.OTF=3;if(!j.win.loaded){j.win.loadPrvtHndlrs.push([q.onWinLoadQuery,q])}j.ev.setTimeout(q.onIntervalQuery,q.intervalLength);}},shouldContinueQuery:function(){var u=this,s=a,r=s.applet,q,t=0,p=0;if(j.win.loaded&&u.count>u.countMax){return p}for(q=0;qu.countMax&&j.codebase.pluginMayBeHanging(r.HTML[q].span)){t=1;r.HTML[q].DELETE=1}if(!t&&!r.results[q][0]&&(r.allowed[q]>=2||(r.allowed[q]==1&&!r.getResult()[0]))&&u.isAppletActive(q)>=0){p=1}}}return p},isJavaActive:function(s){var u=this,r=a,p,q,t=-9;for(p=0;pt){t=q}}return t},isAppletActive:function(t,u){var v=this,q=a,A=q.navigator,p=q.applet,w=p.HTML[t],s=p.active,z,r=0,y,B=s[t];if(u||B>=1.5||!w||!w.span){return B}y=j.DOM.getTagStatus(w,p.DummySpanTagHTML,p.DummyObjTagHTML,p.DummyObjTagHTML2,v.count,v.winLoaded);for(z=0;z0){r=1}}if(y!=1){B=y}else{if(j.browser.isIE||(q.version0&&A.javaEnabled()&&A.mimeObj&&(w.tagName=="object"||r))){B=1}else{B=0}}s[t]=B;return B},onWinLoadQuery:function(p,q){p.ev.setTimeout(q.onWinLoadQuery2,Math.floor(q.intervalLength/2))},onWinLoadQuery2:function(){var p=a.NOTF;p.winLoaded=1;if(a.OTF==3){p.queryCompleted(p.queryAllApplets())}},onIntervalQuery:function(){var q=a.NOTF,p;q.count++;if(a.OTF==3){p=q.queryAllApplets();if(!q.shouldContinueQuery()){q.queryCompleted(p)}}if(a.OTF==3){j.ev.setTimeout(q.onIntervalQuery,q.intervalLength)}},queryAllApplets:function(){var t=this,s=a,r=s.applet,q,p;for(q=0;q=4){return}s.OTF=4;t.isJavaActive();for(p=0;p=30226){p[0]="2"}q=p.join(",")}if(q){t.version=q}return t}},axo:{hasRun:0,installed:0,version:null,progID:"AgControl.AgControl",maxdigit:[20,10,10,100,100,10],mindigit:[0,0,0,0,0,0],IsVersionSupported:function(s,q){var p=this;try{return p.testVersion?j.compareNums(j.formatNum(p.testVersion.join(",")),j.formatNum(q.join(",")))>=0:s.IsVersionSupported(p.format(q))}catch(r){}return 0},format:function(q){var p=this;return(q[0]+"."+q[1]+"."+q[2]+p.make2digits(q[3])+p.make2digits(q[4])+"."+q[5])},make2digits:function(p){return(p<10?"0":"")+p+""},query:function(){var r=this,q,v,s=r.hasRun;r.hasRun=1;if(s){return r}v=j.getAXO(r.progID);if(v){r.installed=1}if(v&&r.IsVersionSupported(v,r.mindigit)){var p=[].concat(r.mindigit),u,t=0;for(q=0;q1&&u<20){u++;t++;p[q]=Math.round((r.maxdigit[q]+r.mindigit[q])/2);if(r.IsVersionSupported(v,p)){r.mindigit[q]=p[q]}else{r.maxdigit[q]=p[q]}}p[q]=r.mindigit[q]}r.version=r.format(p);}return r}}};j.addPlugin("silverlight",h);var f={compareNums:function(s,r){var A=s.split(j.splitNumRegx),y=r.split(j.splitNumRegx),w,q,p,v,u,z;for(w=0;w0)?RegExp.$2.charCodeAt(0):-1;z=/([\d]+)([a-z]?)/.test(y[w]);p=parseInt(RegExp.$1,10);u=(w==2&&RegExp.$2.length>0)?RegExp.$2.charCodeAt(0):-1;if(q!=p){return(q>p?1:-1)}if(w==2&&v!=u){return(v>u?1:-1)}}return 0},setPluginStatus:function(r,p,s){var q=this;q.installed=p?1:(s?(s>0?0.7:-0.1):(r?0:-1));if(p){q.version=j.formatNum(p)}q.getVersionDone=q.installed==0.7||q.installed==-0.1?0:1;j.codebase.emptyGarbage();},getVersion:function(s){var t=this,r,p=null,q;if((!r||j.dbug)&&t.nav.query().installed){r=1}if((!p||j.dbug)&&t.nav.query().version){p=t.nav.version}if((!r||j.dbug)&&t.axo.query().installed){r=1}if((!p||j.dbug)&&t.axo.query().version){p=t.axo.version}if(!p||j.dbug){q=t.codebase.isMin(s);if(q){t.setPluginStatus(0,0,q);return}}if(!p||j.dbug){q=t.codebase.search();if(q){r=1;p=q}}t.setPluginStatus(r,p,0)},nav:{hasRun:0,installed:0,version:null,mimeType:["application/x-vlc-plugin","application/x-google-vlc-plugin","application/mpeg4-muxcodetable","application/x-matroska","application/xspf+xml","video/divx","video/webm","video/x-mpeg","video/x-msvideo","video/ogg","audio/x-flac","audio/amr","audio/amr"],find:"VLC.*Plug-?in",find2:"VLC|VideoLAN",avoid:"Totem|Helix",plugins:["VLC Web Plugin","VLC Multimedia Plug-in","VLC Multimedia Plugin","VLC multimedia plugin"],query:function(){var s=this,p,r,q=s.hasRun||!j.hasMimeType(s.mimeType);s.hasRun=1;if(q){return s}r=j.pd.findNavPlugin({find:s.find,avoid:s.avoid,mimes:s.mimeType,plugins:s.plugins});if(r){s.installed=1;if(r.description){p=j.getNum(r.description+"","[\\d][\\d\\.]*[a-z]*")}if(p){s.version=p}}return s}},axo:{hasRun:0,installed:0,version:null,progID:"VideoLAN.VLCPlugin",query:function(){var q=this,s,p,r=q.hasRun;q.hasRun=1;if(r){return q}s=j.getAXO(q.progID);if(s){q.installed=1;p=j.getNum(j.pd.getPROP(s,"VersionInfo"),"[\\d][\\d\\.]*[a-z]*");if(p){q.version=p}}return q}},codebase:{classID:"clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921",isMin:function(p){this.$$=f;return j.codebase.isMin(this,p)},search:function(){this.$$=f;return j.codebase.search(this)},DIGITMAX:[[11,11,16]],DIGITMIN:[0,0,0,0],Upper:["999"],Lower:["0"],convert:[1]}};j.addPlugin("vlc",f);var c={setPluginStatus:function(){var t=this,q=t.nav.detected,p=t.nav.version,v=p,r=q>0;var u=t.axo.detected,s=t.axo.version,x=t.doc.detected,w=t.doc.version;v=v||s||w;r=r||u>0||x>0;v=v||null;t.version=j.formatNum(v);r=v?1:(r?0:-1);if(r==-1){r=u==-0.5||x==-0.5?-0.15:(j.browser.isIE&&(!j.browser.ActiveXEnabled||j.browser.ActiveXFilteringEnabled)?-1.5:-1)}t.installed=r;},getVersion:function(){var p=this,q=0;if((!q||j.dbug)&&p.nav.query().version){q=1}if((!q||j.dbug)&&p.axo.query().version){q=1}if((!q||j.dbug)&&p.doc.query().version){q=1}p.setPluginStatus()},nav:{detected:0,version:null,mimeType:["application/pdf","application/vnd.adobe.pdfxml"],find:"Adobe.*PDF.*Plug-?in|Adobe.*Acrobat.*Plug-?in|Adobe.*Reader.*Plug-?in",plugins:["Adobe Acrobat","Adobe Acrobat and Reader Plug-in","Adobe Reader Plugin"],query:function(){var r=this,q,p=null;if(r.detected||!j.hasMimeType(r.mimeType)){return r}q=j.pd.findNavPlugin({find:r.find,mimes:r.mimeType,plugins:r.plugins});r.detected=q?1:-1;if(q){p=j.getNum(q.description)||j.getNum(q.name);p=j.getPluginFileVersion(q,p);if(!p){p=r.attempt3()}if(p){r.version=p}}return r},attempt3:function(){var p=null;if(j.OS==1){if(j.hasMimeType("application/vnd.adobe.pdfxml")){p="9"}else{if(j.hasMimeType("application/vnd.adobe.x-mars")){p="8"}else{if(j.hasMimeType("application/vnd.adobe.xfdf")){p="6"}}}}return p}},activexQuery:function(w){var u="",q=null,t,p,s,r;try{if(w){u=w.GetVersions();}}catch(v){}if(u&&j.isString(u)){t=/\=\s*[\d\.]+/g;r=u.match(t);if(r){for(p=0;p0)){q=s}}}}return q},axo:{detected:0,version:null,progID:["AcroPDF.PDF","AcroPDF.PDF.1","PDF.PdfCtrl","PDF.PdfCtrl.5","PDF.PdfCtrl.1"],progID_dummy:"AcroDUMMY.DUMMY",query:function(){var s=this,q=c,t,r=null,p,u;if(s.detected){return s}s.detected=-1;t=j.getAXO(s.progID_dummy);if(!t){u=j.errObj}for(p=0;p0||q?1:(v==-0.1||v==-0.5?-0.5:-1);u.version=q?q:null;return u}}};j.addPlugin("adobereader",c);var l={OTF:null,detectIE3P:0,setPluginStatus:function(){var p=this,q=p.OTF,u=p.doc.result,t=p.mime.result,s=u>0||t>0;var r=p.axo.result;s=s||r>0;p.version=null;if(q==3){p.installed=-0.5}else{s=s?0:-1;if(s==-1){s=r==-0.5||u==-0.5?-0.15:(j.browser.isIE&&(!j.browser.ActiveXEnabled||j.browser.ActiveXFilteringEnabled||!p.detectIE3P)?-1.5:-1)}p.installed=s}if(p.verify&&p.verify.isEnabled()){p.getVersionDone=0}else{if(p.getVersionDone!=1){p.getVersionDone=(p.installed==-0.5||(p.installed==-1&&p.doc.isDisabled()<2))?0:1}}},getVersion:function(s,r,t){var p=this,q=false,v=p.doc,u=p.verify;if(j.isDefined(t)){p.detectIE3P=t?1:0}if(p.getVersionDone===null){p.OTF=0;if(u){u.init()}}j.file.save(p,".pdf",r);if(p.getVersionDone===0){if(u&&u.isEnabled()&&j.isNum(p.installed)&&p.installed>=0){return}if(v.insertHTMLQuery()>0){q=true}p.setPluginStatus();return}if((!q||j.dbug)&&p.mime.query()>0){q=true}if((!q||j.dbug)&&p.axo.query()>0){q=true}if((!q||j.dbug)&&v.insertHTMLQuery()>0){q=true}p.setPluginStatus()},mime:{mimeType:"application/pdf",result:0,query:function(){var p=this;if(!p.result){p.result=j.hasMimeType(p.mimeType)?1:-1;}return p.result}},axo:{result:0,progID:["AcroPDF.PDF","AcroPDF.PDF.1","PDF.PdfCtrl","PDF.PdfCtrl.5","PDF.PdfCtrl.1"],progID_dummy:"AcroDUMMY.DUMMY",prodID3rd:["NitroPDF.IE.ActiveDoc","PDFXCviewIEPlugin.CoPDFXCviewIEPlugin","PDFXCviewIEPlugin.CoPDFXCviewIEPlugin.1","FoxitReader.FoxitReaderCtl","FoxitReader.FoxitReaderCtl.1","FOXITREADEROCX.FoxitReaderOCXCtrl","FOXITREADEROCX.FoxitReaderOCXCtrl.1"],query:function(){var r=this,q=l,p,s;if(!r.result){r.result=-1;if(!j.getAXO(r.progID_dummy)){s=j.errObj}for(p=0;p=2||!j.DOM.isEnabled.objectTag()){s=2}else{if(j.dbug||j.hasMimeType(t.mimeType)){}else{if((q.isGecko&&j.compareNums(q.verGecko,j.formatNum("10"))<=0&&j.OS<=4)||(q.isOpera&&j.compareNums(q.verOpera,j.formatNum("11"))<=0&&j.OS<=4)||(q.isChrome&&j.compareNums(q.verChrome,j.formatNum("10"))<0&&j.OS<=4)){s=2}}}if(s<2){r=j.file.getValid(p);if(!r||!r.full){s=1}}return s},queryObject:function(s,r){var u=this,q=l,p=0,t=1;p=j.DOM.getTagStatus(u.HTML,u.DummySpanTagHTML,u.DummyObjTagHTML1,0,s,r,t);u.result=p;return p},insertHTMLQuery:function(){var t=this,p=l,r,s=1,q=j.DOM.altHTML;if(t.isDisabled()){return t.result}if(p.OTF<2){p.OTF=2}r=j.file.getValid(p).full;if(!t.DummySpanTagHTML){t.DummySpanTagHTML=j.DOM.insert("",[],[],q,p,s)}if(!t.HTML){t.HTML=j.DOM.insert("object",(j.browser.isIE&&!p.detectIE3P?["classid",t.classID]:["type",t.mimeType]).concat(["data",r]),["src",r],q,p,s)}if(!t.DummyObjTagHTML1){t.DummyObjTagHTML1=j.DOM.insert("object",(j.browser.isIE&&!p.detectIE3P?["classid",t.classID_dummy]:["type",t.mimeType_dummy]),[],q,p,s)}t.queryObject();if(j.browser.isIE&&t.result===0){t.HTML.span.innerHTML=t.HTML.outerHTML;t.DummyObjTagHTML1.span.innerHTML=t.DummyObjTagHTML1.outerHTML;t.queryObject()}if((t.result>0||t.result<-0.1)&&!j.dbug){return t.result}p.NOTF.init();return t.result}},NOTF:{winLoaded:0,count:0,countMax:25,intervalLength:250,init:function(){var r=this,p=l,q=p.doc;if(p.OTF<3&&q.HTML){p.OTF=3;if(!j.win.loaded){j.win.loadPrvtHndlrs.push([r.onWinLoadQuery,r])}j.ev.setTimeout(r.onIntervalQuery,r.intervalLength);}},onIntervalQuery:function(){var p=l.doc,q=l.NOTF;q.count++;if(l.OTF==3){p.queryObject(q.count,q.winLoaded);if(p.result>0||p.result<-0.1||(j.win.loaded&&q.count>q.countMax)){q.queryCompleted()}}if(l.OTF==3){j.ev.setTimeout(q.onIntervalQuery,q.intervalLength)}},onWinLoadQuery:function(p,q){p.ev.setTimeout(q.onWinLoadQuery2,Math.floor(q.intervalLength/2))},onWinLoadQuery2:function(){var p=l.doc,q=l.NOTF;q.winLoaded=1;if(l.OTF==3){p.queryObject(q.count,q.winLoaded);q.queryCompleted()}},queryCompleted:function(){var q=this,p=l;if(p.OTF==4){return}p.OTF=4;p.setPluginStatus();j.ev.callArray(p.DoneHndlrs);}}};j.addPlugin("pdfreader",l);var n={mimeType:["audio/x-pn-realaudio-plugin","audio/x-pn-realaudio"],classID:"clsid:CFCDAA03-8BE4-11cf-B84B-0020AFBBCCFA",setPluginStatus:function(r,p){var s=this,q;if(p){s.version=j.formatNum(j.getNum(p))}s.installed=s.version?1:(r?0:-1);q=s.installed==-1||s.instance.version;q=q||s.axo.version;s.getVersionDone=q?1:0;},navObj:{hasRun:0,installed:null,version:null,find:"RealPlayer.*Plug-?in",avoid:"Totem|QuickTime|Helix|VLC|Download",plugins:["RealPlayer(tm) G2 LiveConnect-Enabled Plug-In (32-bit) ","RealPlayer(tm) G2 LiveConnect-Enabled Plug-In (64-bit) ","RealPlayer Plugin"],query:function(){var q=this,s=n,r,p=!q.hasRun&&j.hasMimeType(s.mimeType);q.hasRun=1;if(p){r=j.pd.findNavPlugin({find:q.find,avoid:q.avoid,mimes:s.mimeType,plugins:q.plugins});q.installed=r?1:0;r=j.getPluginFileVersion(r);if(r&&j.compareNums(j.formatNum(r),j.formatNum("15"))>=0){q.version=r}}return q}},JS:{hasRun:0,version:null,regStr:"RealPlayer.*Version.*Plug-?in",mimetype:"application/vnd.rn-realplayer-javascript",q1:[[11,0,0],[999],[663],[663],[663],[660],[468],[468],[468],[468],[468],[468],[431],[431],[431],[372],[180],[180],[172],[172],[167],[114],[0]],q3:[[6,0],[12,99],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,69],[12,46],[12,46],[12,46],[11,3006],[11,2806],[11,2806],[11,2804],[11,2804],[11,2799],[11,2749],[11,2700]],compare:function(t,s){var r,q=t.length,v=s.length,p,u;for(r=0;ru){return 1}if(pq[0].length?u.slice(q[0].length):[];if(v.compare(s,q[1])>0||v.compare(s,q[q.length-1])<0){return r}for(p=q.length-1;p>=1;p--){if(p==1){break}if(v.compare(q[p],s)===0&&v.compare(q[p],q[p-1])===0){break}if(v.compare(s,q[p])>=0&&v.compare(s,q[p-1])<0){break}}return w[0].join(".")+"."+w[p].join(".")},isEnabled:function(){var p=this;return !p.hasRun&&j.OS==1&&j.hasMimeType(p.mimetype)?1:0},query:function(){var u=this,t,r,s,p=u.isEnabled();u.hasRun=1;if(p){r=j.pd.findNavPlugin({find:u.regStr,mimes:u.mimetype});if(r){t=j.formatNum(j.getNum(r.description))}if(t){var q=t.split(j.splitNumRegx);s=1;if(u.compare(q,[6,0,12,200])<0){s=-1}else{if(u.compare(q,[6,0,12,1739])<=0&&u.compare(q,[6,0,12,857])>=0){s=-1}}if(s<0){r=u.convertNum(t,u.q3,u.q1);u.version=r?r:t}}}return u}},instance:{hasRun:0,version:null,HTML:null,isEnabled:function(){var q=this,r=n,p=1;if(!j.DOM.isEnabled.objectTag()){p=0}else{if(j.dbug){}else{if(q.hasRun||j.DOM.isEnabled.objectTagUsingActiveX()||!j.hasMimeType(r.mimeType)||(j.browser.isGecko&&j.compareNums(j.browser.verGecko,j.formatNum("1,8"))<0)||(j.browser.isOpera&&j.compareNums(j.browser.verOpera,j.formatNum("10"))<0)){p=0}}}return p},query:function(){var p=this,t=n,s,q=p.isEnabled();p.hasRun=1;if(q){p.HTML=j.DOM.insert("object",["type",t.mimeType[0]],["src","","autostart","false","imagestatus","false","controls","stopbutton"],"",t);s=p.HTML.obj();try{p.version=j.getNum(s.GetVersionInfo())}catch(r){}j.DOM.setStyle(s,["display","none"]);}return p}},axo:{hasRun:0,installed:null,version:null,progID:["rmocx.RealPlayer G2 Control","rmocx.RealPlayer G2 Control.1","RealPlayer.RealPlayer(tm) ActiveX Control (32-bit)","RealVideo.RealVideo(tm) ActiveX Control (32-bit)","RealPlayer"],query:function(){var r=this,t,p,q;if(!r.hasRun){r.hasRun=1;for(p=0;p0?0.7:-0.1):(q?-3:-1));j.codebase.emptyGarbage();},getVersion:function(t,u,y){var z=this,q=null,v=null,w,s,r,p="";if(j.codebase.isDisabled()){z.setPluginStatus(0,0);return}if(u&&j.isString(u)&&(/[^\s]+/).test(u)){u=u.replace(/\s/g,"");p=u.replace(/[\:\-\/]/g,"$")}else{z.setPluginStatus(0,0,1);return}if(j.isArray(y)){if(!y.length){y.push(0)}for(w=0;w99999999){z.setPluginStatus(0,0,1);return}}if(p&&z.storage[p]){s=z.storage[p].codebase;r=0;for(w=0;w(w0?0:-1}if(q.verify&&q.verify.isEnabled()){q.getVersionDone=0}else{if(q.getVersionDone!=1){q.getVersionDone=(q.installed==-0.5||(q.installed==-1&&q.doc.isDisabled()<2))?0:1}}},getVersion:function(r,q){var s=this,p=false,u=s.verify,t=s.doc;if(s.getVersionDone===null){s.OTF=0;if(u){u.init()}}j.file.save(s,".pdf",q);if(s.getVersionDone===0){if(u&&u.isEnabled()&&j.isNum(s.installed)&&s.installed>=0){return}}if((!p||j.dbug)&&t.insertHTMLQuery()>0){p=true}s.setPluginStatus()},doc:{result:0,mimeType:"application/pdf",mimeType_dummy:"application/dummymimepdf",DummySpanTagHTML:0,HTML:0,DummyObjTagHTML1:0,isDisabled:function(){var t=this,s=b,r=0,p=j.browser,q;if(s.OTF>=2||!j.DOM.isEnabled.objectTag()||j.DOM.isEnabled.objectTagUsingActiveX()){r=2}else{if(j.dbug){}else{if(!p.isGecko||j.compareNums(p.verGecko,j.formatNum("10"))<0||(j.compareNums(p.verGecko,j.formatNum("19"))<0&&j.hasMimeType(t.mimeType))){r=2}}}if(r<2){q=j.file.getValid(s);if(!q||!q.full){r=1}}return r},tabIndex:null,method:"",queryObject:function(s,p){var v=this,u=v.HTML?v.HTML.obj():0,w,r,q=j.dbug&&!j.win.loaded?0:1;w=j.DOM.getTagStatus(v.HTML,v.DummySpanTagHTML,v.DummyObjTagHTML1,0,s,p);if((!v.result||j.dbug)&&w<-0.1){if(q){v.result=-1}v.method+="1,";}if((!v.result||j.dbug)&&w>0&&!j.hasMimeType(v.mimeType)){if(q){v.result=1}v.method+="2,";}try{r=u?u.tabIndex:null}catch(t){}if(!j.isNum(v.tabIndex)&&j.isNum(r)){v.tabIndex=r}if((!v.result||j.dbug)&&w>0&&j.isNum(r)&&j.isNum(v.tabIndex)&&v.tabIndex!==r){if(q){v.result=1}v.method+="4,";}return v.result},insertHTMLQuery:function(){var t=this,r=b,q,s=1,p=j.DOM.altHTML;if(t.isDisabled()){return t.result}if(r.OTF<2){r.OTF=2}q=j.file.getValid(r).full;if(!t.DummySpanTagHTML){t.DummySpanTagHTML=j.DOM.insert("",[],[],p,r,s)}if(!t.HTML){t.HTML=j.DOM.insert("object",["type",t.mimeType,"data",q],["src",q],p,r,s)}if(!t.DummyObjTagHTML1){t.DummyObjTagHTML1=j.DOM.insert("object",["type",t.mimeType_dummy],[],p,r,s)}t.queryObject();if(t.result&&!j.dbug){return t.result}r.NOTF.init();return t.result}},NOTF:{winLoaded:0,count:0,countMax:25,intervalLength:250,init:function(){var r=this,p=b,q=p.doc;if(p.OTF<3&&q.HTML){p.OTF=3;if(!j.win.loaded){j.win.loadPrvtHndlrs.push([r.onWinLoadQuery,r])}j.ev.setTimeout(r.onIntervalQuery,r.intervalLength);}},onIntervalQuery:function(){var p=b.doc,q=b.NOTF;q.count++;if(b.OTF==3){p.queryObject(q.count,q.winLoaded);if(p.result||(j.win.loaded&&q.count>q.countMax)){q.queryCompleted()}}if(b.OTF==3){j.ev.setTimeout(q.onIntervalQuery,q.intervalLength)}},onWinLoadQuery:function(p,q){p.ev.setTimeout(q.onWinLoadQuery2,Math.floor(q.intervalLength/2))},onWinLoadQuery2:function(){var p=b.doc,q=b.NOTF;q.winLoaded=1;if(b.OTF==3){p.queryObject(q.count,q.winLoaded);q.queryCompleted()}},queryCompleted:function(){var q=this,p=b;if(p.OTF==4){return}p.OTF=4;p.setPluginStatus();j.ev.callArray(p.DoneHndlrs);}}};j.addPlugin("pdfjs",b);})(); \ No newline at end of file diff --git a/website/public/javascripts/audio.js b/website/public/javascripts/audio.js new file mode 100644 index 0000000..c169773 --- /dev/null +++ b/website/public/javascripts/audio.js @@ -0,0 +1,159 @@ +var audioData = {}; + +if ((window.AudioContext || window.webkitAudioContext) === undefined){ + audioData = "Not supported"; +} else { + // Performs fingerprint as found in https://client.a.pxi.pub/PXmssU3ZQ0/main.min.js + //Sum of buffer values + function run_pxi_fp() { + try { + if (context = new (window.OfflineAudioContext || window.webkitOfflineAudioContext)(1, 44100, 44100), !context) { + audioData.pxi_output = 0; + } + + // Create oscillator + pxi_oscillator = context.createOscillator(); + pxi_oscillator.type = "triangle"; + pxi_oscillator.frequency.value = 1e4; + + // Create and configure compressor + pxi_compressor = context.createDynamicsCompressor(); + pxi_compressor.threshold && (pxi_compressor.threshold.value = -50); + pxi_compressor.knee && (pxi_compressor.knee.value = 40); + pxi_compressor.ratio && (pxi_compressor.ratio.value = 12); + pxi_compressor.reduction && (pxi_compressor.reduction.value = -20); + pxi_compressor.attack && (pxi_compressor.attack.value = 0); + pxi_compressor.release && (pxi_compressor.release.value = .25); + + // Connect nodes + pxi_oscillator.connect(pxi_compressor); + pxi_compressor.connect(context.destination); + + // Start audio processing + pxi_oscillator.start(0); + context.startRendering(); + context.oncomplete = function (evnt) { + audioData.pxi_output = 0; + var sha1 = CryptoJS.algo.SHA1.create(); + for (var i = 0; i < evnt.renderedBuffer.length; i++) { + sha1.update(evnt.renderedBuffer.getChannelData(0)[i].toString()); + } + hash = sha1.finalize(); + audioData.pxi_full_buffer_hash = hash.toString(CryptoJS.enc.Hex); + for (var i = 4500; 5e3 > i; i++) { + audioData.pxi_output += Math.abs(evnt.renderedBuffer.getChannelData(0)[i]); + } + pxi_compressor.disconnect(); + } + } catch (u) { + audioData.pxi_output = 0; + } + } + + // End PXI fingerprint + + // Performs fingerprint as found in some versions of http://metrics.nt.vc/metrics.js + function a(a, b, c) { + for (var d in b) "dopplerFactor" === d || "speedOfSound" === d || "currentTime" === + d || "number" !== typeof b[d] && "string" !== typeof b[d] || (a[(c ? c : "") + d] = b[d]); + return a + } + + function run_nt_vc_fp() { + try { + var nt_vc_context = window.AudioContext || window.webkitAudioContext; + if ("function" !== typeof nt_vc_context) audioData.nt_vc_output = "Not available"; + else { + var f = new nt_vc_context, + d = f.createAnalyser(); + audioData.nt_vc_output = a({}, f, "ac-"); + audioData.nt_vc_output = a(audioData.nt_vc_output, f.destination, "ac-"); + audioData.nt_vc_output = a(audioData.nt_vc_output, f.listener, "ac-"); + audioData.nt_vc_output = a(audioData.nt_vc_output, d, "an-"); + } + } catch (g) { + audioData.nt_vc_output = 0 + } + } + + // Performs fingerprint as found in https://www.cdn-net.com/cc.js + var cc_output = []; + + function run_cc_fp() { + var audioCtx = new (window.AudioContext || window.webkitAudioContext), + oscillator = audioCtx.createOscillator(), + analyser = audioCtx.createAnalyser(), + gain = audioCtx.createGain(), + scriptProcessor = audioCtx.createScriptProcessor(4096, 1, 1); + + + gain.gain.value = 0; // Disable volume + oscillator.type = "triangle"; // Set oscillator to output triangle wave + oscillator.connect(analyser); // Connect oscillator output to analyser input + analyser.connect(scriptProcessor); // Connect analyser output to scriptProcessor input + scriptProcessor.connect(gain); // Connect scriptProcessor output to gain input + gain.connect(audioCtx.destination); // Connect gain output to audiocontext destination + + scriptProcessor.onaudioprocess = function (bins) { + bins = new Float32Array(analyser.frequencyBinCount); + analyser.getFloatFrequencyData(bins); + for (var i = 0; i < bins.length; i = i + 1) { + cc_output.push(bins[i]); + } + analyser.disconnect(); + scriptProcessor.disconnect(); + gain.disconnect(); + audioData.cc_output = cc_output.slice(0, 30); + }; + + oscillator.start(0); + } + + // Performs a hybrid of cc/pxi methods found above + var hybrid_output = []; + + function run_hybrid_fp() { + var audioCtx = new (window.AudioContext || window.webkitAudioContext), + oscillator = audioCtx.createOscillator(), + analyser = audioCtx.createAnalyser(), + gain = audioCtx.createGain(), + scriptProcessor = audioCtx.createScriptProcessor(4096, 1, 1); + + // Create and configure compressor + compressor = audioCtx.createDynamicsCompressor(); + compressor.threshold && (compressor.threshold.value = -50); + compressor.knee && (compressor.knee.value = 40); + compressor.ratio && (compressor.ratio.value = 12); + compressor.reduction && (compressor.reduction.value = -20); + compressor.attack && (compressor.attack.value = 0); + compressor.release && (compressor.release.value = .25); + + gain.gain.value = 0; // Disable volume + oscillator.type = "triangle"; // Set oscillator to output triangle wave + oscillator.connect(compressor); // Connect oscillator output to dynamic compressor + compressor.connect(analyser); // Connect compressor to analyser + analyser.connect(scriptProcessor); // Connect analyser output to scriptProcessor input + scriptProcessor.connect(gain); // Connect scriptProcessor output to gain input + gain.connect(audioCtx.destination); // Connect gain output to audiocontext destination + + scriptProcessor.onaudioprocess = function (bins) { + bins = new Float32Array(analyser.frequencyBinCount); + analyser.getFloatFrequencyData(bins); + for (var i = 0; i < bins.length; i = i + 1) { + hybrid_output.push(bins[i]); + } + analyser.disconnect(); + scriptProcessor.disconnect(); + gain.disconnect(); + + audioData.hybrid_output = hybrid_output.slice(0, 30); + }; + + oscillator.start(0); + } + + run_pxi_fp(); + run_nt_vc_fp(); + run_cc_fp(); + run_hybrid_fp(); +} \ No newline at end of file diff --git a/website/public/javascripts/audioSha1.js b/website/public/javascripts/audioSha1.js new file mode 100644 index 0000000..f8566f0 --- /dev/null +++ b/website/public/javascripts/audioSha1.js @@ -0,0 +1,15 @@ +/* +CryptoJS v3.1.2 +code.google.com/p/crypto-js +(c) 2009-2013 by Jeff Mott. All rights reserved. +code.google.com/p/crypto-js/wiki/License +*/ +var CryptoJS=CryptoJS||function(e,m){var p={},j=p.lib={},l=function(){},f=j.Base={extend:function(a){l.prototype=this;var c=new l;a&&c.mixIn(a);c.hasOwnProperty("init")||(c.init=function(){c.$super.init.apply(this,arguments)});c.init.prototype=c;c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty("toString")&&(this.toString=a.toString)},clone:function(){return this.init.prototype.extend(this)}}, +n=j.WordArray=f.extend({init:function(a,c){a=this.words=a||[];this.sigBytes=c!=m?c:4*a.length},toString:function(a){return(a||h).stringify(this)},concat:function(a){var c=this.words,q=a.words,d=this.sigBytes;a=a.sigBytes;this.clamp();if(d%4)for(var b=0;b>>2]|=(q[b>>>2]>>>24-8*(b%4)&255)<<24-8*((d+b)%4);else if(65535>>2]=q[b>>>2];else c.push.apply(c,q);this.sigBytes+=a;return this},clamp:function(){var a=this.words,c=this.sigBytes;a[c>>>2]&=4294967295<< +32-8*(c%4);a.length=e.ceil(c/4)},clone:function(){var a=f.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var c=[],b=0;b>>2]>>>24-8*(d%4)&255;b.push((f>>>4).toString(16));b.push((f&15).toString(16))}return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d>>3]|=parseInt(a.substr(d, +2),16)<<24-4*(d%8);return new n.init(b,c/2)}},g=b.Latin1={stringify:function(a){var c=a.words;a=a.sigBytes;for(var b=[],d=0;d>>2]>>>24-8*(d%4)&255));return b.join("")},parse:function(a){for(var c=a.length,b=[],d=0;d>>2]|=(a.charCodeAt(d)&255)<<24-8*(d%4);return new n.init(b,c)}},r=b.Utf8={stringify:function(a){try{return decodeURIComponent(escape(g.stringify(a)))}catch(c){throw Error("Malformed UTF-8 data");}},parse:function(a){return g.parse(unescape(encodeURIComponent(a)))}}, +k=j.BufferedBlockAlgorithm=f.extend({reset:function(){this._data=new n.init;this._nDataBytes=0},_append:function(a){"string"==typeof a&&(a=r.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var c=this._data,b=c.words,d=c.sigBytes,f=this.blockSize,h=d/(4*f),h=a?e.ceil(h):e.max((h|0)-this._minBufferSize,0);a=h*f;d=e.min(4*a,d);if(a){for(var g=0;ga;a++){if(16>a)l[a]=f[n+a]|0;else{var c=l[a-3]^l[a-8]^l[a-14]^l[a-16];l[a]=c<<1|c>>>31}c=(h<<5|h>>>27)+j+l[a];c=20>a?c+((g&e|~g&k)+1518500249):40>a?c+((g^e^k)+1859775393):60>a?c+((g&e|g&k|e&k)-1894007588):c+((g^e^ +k)-899497514);j=k;k=e;e=g<<30|g>>>2;g=h;h=c}b[0]=b[0]+h|0;b[1]=b[1]+g|0;b[2]=b[2]+e|0;b[3]=b[3]+k|0;b[4]=b[4]+j|0},_doFinalize:function(){var f=this._data,e=f.words,b=8*this._nDataBytes,h=8*f.sigBytes;e[h>>>5]|=128<<24-h%32;e[(h+64>>>9<<4)+14]=Math.floor(b/4294967296);e[(h+64>>>9<<4)+15]=b;f.sigBytes=4*e.length;this._process();return this._hash},clone:function(){var e=j.clone.call(this);e._hash=this._hash.clone();return e}});e.SHA1=j._createHelper(m);e.HmacSHA1=j._createHmacHelper(m)})(); \ No newline at end of file diff --git a/website/public/javascripts/blockExtensions.js b/website/public/javascripts/blockExtensions.js new file mode 100644 index 0000000..9b78b9c --- /dev/null +++ b/website/public/javascripts/blockExtensions.js @@ -0,0 +1,134 @@ +var blockExtensionsData = {}; + +function testLink(url) { + + // Define the promise + const imgPromise = new Promise(function imgPromise(resolve, reject) { + let start = performance.now(); + // Create the image + const imgElement = new Image(); + + // When image is loaded, resolve the promise + imgElement.addEventListener('load', function imgOnLoad() { + resolve(this); + }); + + + imgElement.addEventListener('error', function imgOnError(e) { + + resolve(performance.now() - start); + }) + + // Assign URL + imgElement.src = url; + + }); + + return imgPromise; +} + +function funcTimeOut() { + return new Promise((resolve) => { + setTimeout(() => { + return resolve('URL timeout'); + }, 50000) + }) +} + +async function startTest(latest_links, lowe_links, old_links, random_links) { + const latestPromisesArray = []; + const lowePromisesArray = []; + const randomPromisesArray = []; + const oldPromisesArray = []; + + //Test latest_links + for(key in latest_links) { + + let p = Promise.race([ + funcTimeOut(), + testLink(`http://${key}` + escape(new Date())) + ]); + latestPromisesArray.push(p); + }; + const latestRes = await Promise.all(latestPromisesArray); + + //Test lowe_links + for(key in lowe_links) { + + let p = Promise.race([ + funcTimeOut(), + testLink(`http://${key}` + escape(new Date())) + ]); + lowePromisesArray.push(p); + }; + const loweRes = await Promise.all(lowePromisesArray); + + //Test old_links + for(key in old_links) { + + let p = Promise.race([ + funcTimeOut(), + testLink(`http://${key}` + escape(new Date())) + ]); + oldPromisesArray.push(p); + }; + const oldRes = await Promise.all(oldPromisesArray); + + //Test random_links + for(key in random_links) { + + let p = Promise.race([ + funcTimeOut(), + testLink(`http://${key}` + escape(new Date())) + ]); + randomPromisesArray.push(p); + }; + const randomRes = await Promise.all(randomPromisesArray); + + + return [latestRes,loweRes,oldRes,randomRes]; + +}; + +function begin() { + + var url_all_links = "https://gitcdn.link/repo/mishravikas/testLinks/master/all_links.json"; + $.ajax({ + type: 'GET', + dataType: 'json', + crossDomain: true, + url: url_all_links, + success: function (responseData, textStatus, jqXHR) { + var all_links = responseData;; + var latest_links = all_links['latest_links']; + var lowe_links = all_links['lowe_links']; + var old_links = all_links['old_links']; + var random_links = all_links['random_links']; + startTest(latest_links,lowe_links,old_links,random_links).then( function(result) { + + blockExtensionsData['latest_links'] = latest_links; + blockExtensionsData['lowe_links'] = lowe_links; + blockExtensionsData['old_links'] = old_links; + blockExtensionsData['random_links'] = random_links; + blockExtensionsData['latest_results'] = result[0]; + blockExtensionsData['lowe_results'] = result[1]; + blockExtensionsData['old_results'] = result[2]; + blockExtensionsData['random_results'] = result[3]; + + }); + + }, + error: function (responseData, textStatus, errorThrown) { + blockExtensionsData['latest_links'] = "Error"; + blockExtensionsData['lowe_links'] = "Error"; + blockExtensionsData['old_links'] = "Error"; + blockExtensionsData['random_links'] = "Error"; + blockExtensionsData['latest_results'] = "Error"; + blockExtensionsData['lowe_results'] = "Error"; + blockExtensionsData['old_results'] = "Error"; + blockExtensionsData['random_results'] = "Error"; + } + }); +}; + +begin(); diff --git a/website/public/javascripts/bootstrap.min.js b/website/public/javascripts/bootstrap.min.js index 63866bc..c8f82e5 100644 --- a/website/public/javascripts/bootstrap.min.js +++ b/website/public/javascripts/bootstrap.min.js @@ -1,7 +1,7 @@ /*! - * Bootstrap v3.3.0 (http://getbootstrap.com) - * Copyright 2011-2014 Twitter, Inc. + * Bootstrap v3.3.4 (http://getbootstrap.com) + * Copyright 2011-2015 Twitter, Inc. * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) */ -if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){var b=a.fn.jquery.split(" ")[0].split(".");if(b[0]<2&&b[1]<9||1==b[0]&&9==b[1]&&b[2]<1)throw new Error("Bootstrap's JavaScript requires jQuery version 1.9.1 or higher")}(jQuery),+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one("bsTransitionEnd",function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b(),a.support.transition&&(a.event.special.bsTransitionEnd={bindType:a.support.transition.end,delegateType:a.support.transition.end,handle:function(b){return a(b.target).is(this)?b.handleObj.handler.apply(this,arguments):void 0}})})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var c=a(this),e=c.data("bs.alert");e||c.data("bs.alert",e=new d(this)),"string"==typeof b&&e[b].call(c)})}var c='[data-dismiss="alert"]',d=function(b){a(b).on("click",c,this.close)};d.VERSION="3.3.0",d.TRANSITION_DURATION=150,d.prototype.close=function(b){function c(){g.detach().trigger("closed.bs.alert").remove()}var e=a(this),f=e.attr("data-target");f||(f=e.attr("href"),f=f&&f.replace(/.*(?=#[^\s]*$)/,""));var g=a(f);b&&b.preventDefault(),g.length||(g=e.closest(".alert")),g.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(g.removeClass("in"),a.support.transition&&g.hasClass("fade")?g.one("bsTransitionEnd",c).emulateTransitionEnd(d.TRANSITION_DURATION):c())};var e=a.fn.alert;a.fn.alert=b,a.fn.alert.Constructor=d,a.fn.alert.noConflict=function(){return a.fn.alert=e,this},a(document).on("click.bs.alert.data-api",c,d.prototype.close)}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof b&&b;e||d.data("bs.button",e=new c(this,f)),"toggle"==b?e.toggle():b&&e.setState(b)})}var c=function(b,d){this.$element=a(b),this.options=a.extend({},c.DEFAULTS,d),this.isLoading=!1};c.VERSION="3.3.0",c.DEFAULTS={loadingText:"loading..."},c.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",null==f.resetText&&d.data("resetText",d[e]()),setTimeout(a.proxy(function(){d[e](null==f[b]?this.options[b]:f[b]),"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},c.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}else this.$element.attr("aria-pressed",!this.$element.hasClass("active"));a&&this.$element.toggleClass("active")};var d=a.fn.button;a.fn.button=b,a.fn.button.Constructor=c,a.fn.button.noConflict=function(){return a.fn.button=d,this},a(document).on("click.bs.button.data-api",'[data-toggle^="button"]',function(c){var d=a(c.target);d.hasClass("btn")||(d=d.closest(".btn")),b.call(d,"toggle"),c.preventDefault()}).on("focus.bs.button.data-api blur.bs.button.data-api",'[data-toggle^="button"]',function(b){a(b.target).closest(".btn").toggleClass("focus","focus"==b.type)})}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},c.DEFAULTS,d.data(),"object"==typeof b&&b),g="string"==typeof b?b:f.slide;e||d.data("bs.carousel",e=new c(this,f)),"number"==typeof b?e.to(b):g?e[g]():f.interval&&e.pause().cycle()})}var c=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,this.options.keyboard&&this.$element.on("keydown.bs.carousel",a.proxy(this.keydown,this)),"hover"==this.options.pause&&!("ontouchstart"in document.documentElement)&&this.$element.on("mouseenter.bs.carousel",a.proxy(this.pause,this)).on("mouseleave.bs.carousel",a.proxy(this.cycle,this))};c.VERSION="3.3.0",c.TRANSITION_DURATION=600,c.DEFAULTS={interval:5e3,pause:"hover",wrap:!0,keyboard:!0},c.prototype.keydown=function(a){switch(a.which){case 37:this.prev();break;case 39:this.next();break;default:return}a.preventDefault()},c.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},c.prototype.getItemIndex=function(a){return this.$items=a.parent().children(".item"),this.$items.index(a||this.$active)},c.prototype.getItemForDirection=function(a,b){var c="prev"==a?-1:1,d=this.getItemIndex(b),e=(d+c)%this.$items.length;return this.$items.eq(e)},c.prototype.to=function(a){var b=this,c=this.getItemIndex(this.$active=this.$element.find(".item.active"));return a>this.$items.length-1||0>a?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){b.to(a)}):c==a?this.pause().cycle():this.slide(a>c?"next":"prev",this.$items.eq(a))},c.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},c.prototype.next=function(){return this.sliding?void 0:this.slide("next")},c.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},c.prototype.slide=function(b,d){var e=this.$element.find(".item.active"),f=d||this.getItemForDirection(b,e),g=this.interval,h="next"==b?"left":"right",i="next"==b?"first":"last",j=this;if(!f.length){if(!this.options.wrap)return;f=this.$element.find(".item")[i]()}if(f.hasClass("active"))return this.sliding=!1;var k=f[0],l=a.Event("slide.bs.carousel",{relatedTarget:k,direction:h});if(this.$element.trigger(l),!l.isDefaultPrevented()){if(this.sliding=!0,g&&this.pause(),this.$indicators.length){this.$indicators.find(".active").removeClass("active");var m=a(this.$indicators.children()[this.getItemIndex(f)]);m&&m.addClass("active")}var n=a.Event("slid.bs.carousel",{relatedTarget:k,direction:h});return a.support.transition&&this.$element.hasClass("slide")?(f.addClass(b),f[0].offsetWidth,e.addClass(h),f.addClass(h),e.one("bsTransitionEnd",function(){f.removeClass([b,h].join(" ")).addClass("active"),e.removeClass(["active",h].join(" ")),j.sliding=!1,setTimeout(function(){j.$element.trigger(n)},0)}).emulateTransitionEnd(c.TRANSITION_DURATION)):(e.removeClass("active"),f.addClass("active"),this.sliding=!1,this.$element.trigger(n)),g&&this.cycle(),this}};var d=a.fn.carousel;a.fn.carousel=b,a.fn.carousel.Constructor=c,a.fn.carousel.noConflict=function(){return a.fn.carousel=d,this};var e=function(c){var d,e=a(this),f=a(e.attr("data-target")||(d=e.attr("href"))&&d.replace(/.*(?=#[^\s]+$)/,""));if(f.hasClass("carousel")){var g=a.extend({},f.data(),e.data()),h=e.attr("data-slide-to");h&&(g.interval=!1),b.call(f,g),h&&f.data("bs.carousel").to(h),c.preventDefault()}};a(document).on("click.bs.carousel.data-api","[data-slide]",e).on("click.bs.carousel.data-api","[data-slide-to]",e),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var c=a(this);b.call(c,c.data())})})}(jQuery),+function(a){"use strict";function b(b){var c,d=b.attr("data-target")||(c=b.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"");return a(d)}function c(b){return this.each(function(){var c=a(this),e=c.data("bs.collapse"),f=a.extend({},d.DEFAULTS,c.data(),"object"==typeof b&&b);!e&&f.toggle&&"show"==b&&(f.toggle=!1),e||c.data("bs.collapse",e=new d(this,f)),"string"==typeof b&&e[b]()})}var d=function(b,c){this.$element=a(b),this.options=a.extend({},d.DEFAULTS,c),this.$trigger=a(this.options.trigger).filter('[href="#'+b.id+'"], [data-target="#'+b.id+'"]'),this.transitioning=null,this.options.parent?this.$parent=this.getParent():this.addAriaAndCollapsedClass(this.$element,this.$trigger),this.options.toggle&&this.toggle()};d.VERSION="3.3.0",d.TRANSITION_DURATION=350,d.DEFAULTS={toggle:!0,trigger:'[data-toggle="collapse"]'},d.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},d.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b,e=this.$parent&&this.$parent.find("> .panel").children(".in, .collapsing");if(!(e&&e.length&&(b=e.data("bs.collapse"),b&&b.transitioning))){var f=a.Event("show.bs.collapse");if(this.$element.trigger(f),!f.isDefaultPrevented()){e&&e.length&&(c.call(e,"hide"),b||e.data("bs.collapse",null));var g=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[g](0).attr("aria-expanded",!0),this.$trigger.removeClass("collapsed").attr("aria-expanded",!0),this.transitioning=1;var h=function(){this.$element.removeClass("collapsing").addClass("collapse in")[g](""),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return h.call(this);var i=a.camelCase(["scroll",g].join("-"));this.$element.one("bsTransitionEnd",a.proxy(h,this)).emulateTransitionEnd(d.TRANSITION_DURATION)[g](this.$element[0][i])}}}},d.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse in").attr("aria-expanded",!1),this.$trigger.addClass("collapsed").attr("aria-expanded",!1),this.transitioning=1;var e=function(){this.transitioning=0,this.$element.removeClass("collapsing").addClass("collapse").trigger("hidden.bs.collapse")};return a.support.transition?void this.$element[c](0).one("bsTransitionEnd",a.proxy(e,this)).emulateTransitionEnd(d.TRANSITION_DURATION):e.call(this)}}},d.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()},d.prototype.getParent=function(){return a(this.options.parent).find('[data-toggle="collapse"][data-parent="'+this.options.parent+'"]').each(a.proxy(function(c,d){var e=a(d);this.addAriaAndCollapsedClass(b(e),e)},this)).end()},d.prototype.addAriaAndCollapsedClass=function(a,b){var c=a.hasClass("in");a.attr("aria-expanded",c),b.toggleClass("collapsed",!c).attr("aria-expanded",c)};var e=a.fn.collapse;a.fn.collapse=c,a.fn.collapse.Constructor=d,a.fn.collapse.noConflict=function(){return a.fn.collapse=e,this},a(document).on("click.bs.collapse.data-api",'[data-toggle="collapse"]',function(d){var e=a(this);e.attr("data-target")||d.preventDefault();var f=b(e),g=f.data("bs.collapse"),h=g?"toggle":a.extend({},e.data(),{trigger:this});c.call(f,h)})}(jQuery),+function(a){"use strict";function b(b){b&&3===b.which||(a(e).remove(),a(f).each(function(){var d=a(this),e=c(d),f={relatedTarget:this};e.hasClass("open")&&(e.trigger(b=a.Event("hide.bs.dropdown",f)),b.isDefaultPrevented()||(d.attr("aria-expanded","false"),e.removeClass("open").trigger("hidden.bs.dropdown",f)))}))}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}function d(b){return this.each(function(){var c=a(this),d=c.data("bs.dropdown");d||c.data("bs.dropdown",d=new g(this)),"string"==typeof b&&d[b].call(c)})}var e=".dropdown-backdrop",f='[data-toggle="dropdown"]',g=function(b){a(b).on("click.bs.dropdown",this.toggle)};g.VERSION="3.3.0",g.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a('