From 9a03991a21d248780ecbd8a0c0375197417ae36e Mon Sep 17 00:00:00 2001 From: pierre_laperdrix Date: Tue, 2 Dec 2014 10:38:11 +0000 Subject: [PATCH 001/159] README.md edited online with Bitbucket --- README.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..53de2d0 --- /dev/null +++ b/README.md @@ -0,0 +1,18 @@ +# Am I Unique ? Source # + +This repository contains all the source code from the [AmIUnique.org](https://amiunique.org/) website. + +### Requirements ### +**JDK8** is needed to build the application. + +### 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. + +### Run ### +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. + + From 9fe604a14c4aba0adb9c8693fac83845084ed495 Mon Sep 17 00:00:00 2001 From: pierre_laperdrix Date: Tue, 2 Dec 2014 10:46:45 +0000 Subject: [PATCH 002/159] README.md edited online with Bitbucket --- README.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 53de2d0..e905dfd 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,17 @@ # Am I Unique ? Source # -This repository contains all the source code from the [AmIUnique.org](https://amiunique.org/) website. +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. -### Database ### +### 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. -### Run ### +### 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. - - +You may also need to regenerate the Play application secret. To do so, run "**play-update-secret**" in the Play console. \ No newline at end of file From 2c000e3ec77ad831ce3b19cdf34fc5aa62fbe26d Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Tue, 2 Dec 2014 18:30:31 +0100 Subject: [PATCH 003/159] Update of the Readme --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e905dfd..3ab2ab1 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Am I Unique ? Source # +# 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. @@ -14,4 +14,4 @@ Same action required in "**amiunique-source /website/conf/META-INF/persistence.x ### 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. \ No newline at end of file +You may also need to regenerate the Play application secret. To do so, run "**play-update-secret**" in the Play console. From 60593f5a6a9b598ecc34ef6b84202b5cca799f9e Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Tue, 2 Dec 2014 18:31:45 +0100 Subject: [PATCH 004/159] Source of the OSData.swf added --- OSData.as | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 OSData.as 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; + } + } +} From 5bf7944332967e76d1d79d0c9ba5908ec2db02e1 Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Wed, 3 Dec 2014 14:30:40 +0100 Subject: [PATCH 005/159] Fixed incorrect font color when hovering tabs in the menu + Added GitHub repo link in the menu --- website/app/views/about.scala.html | 17 ++++++---- website/app/views/faq.scala.html | 51 +++++++++++++++------------- website/app/views/fp.scala.html | 17 ++++++---- website/app/views/fpNoJs.scala.html | 19 ++++++----- website/app/views/home.scala.html | 26 +++++++------- website/app/views/links.scala.html | 17 ++++++---- website/app/views/privacy.scala.html | 17 ++++++---- website/app/views/stats.scala.html | 17 ++++++---- 8 files changed, 100 insertions(+), 81 deletions(-) diff --git a/website/app/views/about.scala.html b/website/app/views/about.scala.html index e809a5d..e014872 100644 --- a/website/app/views/about.scala.html +++ b/website/app/views/about.scala.html @@ -40,25 +40,28 @@ diff --git a/website/app/views/faq.scala.html b/website/app/views/faq.scala.html index db06496..08743f1 100644 --- a/website/app/views/faq.scala.html +++ b/website/app/views/faq.scala.html @@ -38,30 +38,33 @@ + + diff --git a/website/app/views/fp.scala.html b/website/app/views/fp.scala.html index 5b79fce..c83f8f7 100644 --- a/website/app/views/fp.scala.html +++ b/website/app/views/fp.scala.html @@ -140,25 +140,28 @@ diff --git a/website/app/views/fpNoJs.scala.html b/website/app/views/fpNoJs.scala.html index 9f9fe24..1c3b689 100644 --- a/website/app/views/fpNoJs.scala.html +++ b/website/app/views/fpNoJs.scala.html @@ -39,25 +39,28 @@ diff --git a/website/app/views/home.scala.html b/website/app/views/home.scala.html index 5c43bc6..0f220f6 100644 --- a/website/app/views/home.scala.html +++ b/website/app/views/home.scala.html @@ -40,25 +40,28 @@ @@ -69,12 +72,6 @@
- @Html(Messages("home"))
@@ -86,7 +83,8 @@ diff --git a/website/app/views/links.scala.html b/website/app/views/links.scala.html index e8b11fc..10604be 100644 --- a/website/app/views/links.scala.html +++ b/website/app/views/links.scala.html @@ -40,25 +40,28 @@ diff --git a/website/app/views/privacy.scala.html b/website/app/views/privacy.scala.html index 1ecc172..0d83806 100644 --- a/website/app/views/privacy.scala.html +++ b/website/app/views/privacy.scala.html @@ -40,25 +40,28 @@ diff --git a/website/app/views/stats.scala.html b/website/app/views/stats.scala.html index ffbf959..0b75000 100644 --- a/website/app/views/stats.scala.html +++ b/website/app/views/stats.scala.html @@ -49,25 +49,28 @@ From 5bb0548cbf97d88b7784aaa1c2a7b6ff7f917866 Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Wed, 3 Dec 2014 14:31:11 +0100 Subject: [PATCH 006/159] License added --- LICENSE.md | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 LICENSE.md 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. + From 3f90230442247a70a1690246479d2a135ebf2065 Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Mon, 8 Dec 2014 17:36:19 +0100 Subject: [PATCH 007/159] Changed the way SQL queries were handled for improved security --- website/app/models/FpDataEntityManager.java | 158 ++++++++++---------- 1 file changed, 82 insertions(+), 76 deletions(-) diff --git a/website/app/models/FpDataEntityManager.java b/website/app/models/FpDataEntityManager.java index 1b0b813..d04eadc 100644 --- a/website/app/models/FpDataEntityManager.java +++ b/website/app/models/FpDataEntityManager.java @@ -3,6 +3,7 @@ import com.fasterxml.jackson.databind.JsonNode; import play.db.jpa.JPA; import javax.persistence.EntityManager; +import javax.persistence.Query; import java.math.BigInteger; import java.sql.Timestamp; import java.util.*; @@ -26,33 +27,51 @@ public boolean checkIfFPExists(String id,String userAgentHttp, 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 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 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 -> { + Query q = em.createQuery(query) + .setParameter("id", id).setParameter("acceptHttp",acceptHttp).setParameter("userAgentHttp", userAgentHttp) + .setParameter("connectionHttp", connectionHttp).setParameter("encodingHttp", encodingHttp); + q.setParameter("languageHttp",languageHttp).setParameter("pluginsJs",pluginsJs).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("canvasJs",canvasJs).setParameter("webGljs",webGljs); + q.setParameter("fontsFlash", fontsFlash).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 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 connectionHttp= :connectionHttp " + + "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("connectionHttp", connectionHttp).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()); + 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)); } @@ -133,7 +152,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,8 +165,10 @@ 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 { @@ -159,39 +179,31 @@ public Map getPercentages(JsonNode values) { 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,28 +211,22 @@ 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 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 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(); + VersionMap langMap = new VersionMap(); for(int i=0; i> getOSBrowserStats(){ + public HashMap> getOSBrowserStats(){ String query = "SELECT userAgentHttp FROM FpDataEntity"; ArrayList userAgents = withTransaction(em -> ((ArrayList) em.createQuery(query).getResultList())); /* 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("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 */ @@ -265,16 +271,16 @@ public HashMap> getOSBrowserStats(){ os.get(ua.getOs()).add(ua.getOsVersion()); } - HashMap> 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(); + RangeMap nbFontsMap = new RangeMap(); for(int i=0; i Date: Mon, 8 Dec 2014 17:38:20 +0100 Subject: [PATCH 008/159] Improved stats generation to reduce server load --- website/app/controllers/Application.java | 146 +++++++----- website/app/models/CounterMap.java | 38 +++ website/app/models/ParsedFP.java | 218 ++++++++++-------- website/app/models/RangeComparator.java | 32 +++ website/app/models/RangeMap.java | 9 + website/app/models/Rangemap.java | 31 --- website/app/models/Stats.java | 65 ++++++ .../{Vermap.java => VersionComparator.java} | 30 +-- website/app/models/VersionMap.java | 10 + website/app/views/results.scala.html | 8 +- website/app/views/stats.scala.html | 8 +- 11 files changed, 382 insertions(+), 213 deletions(-) create mode 100644 website/app/models/CounterMap.java create mode 100644 website/app/models/RangeComparator.java create mode 100644 website/app/models/RangeMap.java delete mode 100644 website/app/models/Rangemap.java create mode 100644 website/app/models/Stats.java rename website/app/models/{Vermap.java => VersionComparator.java} (63%) create mode 100644 website/app/models/VersionMap.java diff --git a/website/app/controllers/Application.java b/website/app/controllers/Application.java index 4968785..d8d83e3 100644 --- a/website/app/controllers/Application.java +++ b/website/app/controllers/Application.java @@ -14,7 +14,6 @@ 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; @@ -49,6 +48,14 @@ public static Result about(){ return ok(about.render()); } + public static String getHeader(Http.Request request, String header){ + if(request.getHeader(header) == null){ + return "Not specified"; + } else { + return request.getHeader(header); + } + } + public static Result fpNoJs() { Http.Cookie cookie = request().cookies().get("amiunique"); String id; @@ -60,19 +67,22 @@ public static Result fpNoJs() { FpDataEntityManager em = new FpDataEntityManager(); FpDataEntity fp; + boolean newFp; - 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"))){ + if(!id.equals("Not supported") && em.checkIfFPWithNoJsExists(id, getHeader(request(), "User-Agent"), + getHeader(request(), "Accept"), getHeader(request(), "Connection"), + getHeader(request(), "Accept-Encoding"), getHeader(request(), "Accept-Language"))){ fp = em.getExistingFP(id); + newFp = false; } 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"), + DigestUtils.sha1Hex(request().remoteAddress()), 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("[,\\[\\]]", "")); + newFp = true; } ObjectNode node = (ObjectNode) Json.toJson(fp); @@ -86,39 +96,54 @@ public static Result fpNoJs() { 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()); + 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 + Integer nbTotal = s.getNbTotal(); + //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(); + HashMap osMap = s.getOs(); + HashMap browsersMap = s.getBrowsers(); + VersionMap langMap = s.getLanguages(); //Adding percentages for OS and browsers - for (Map.Entry entry : osMap.entrySet()) { + for (Map.Entry entry : osMap.entrySet()) { percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); } - for (Map.Entry entry : browsersMap.entrySet()) { + for (Map.Entry entry : browsersMap.entrySet()) { percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); } - //Render the FP + Stats + //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 String getAttribute(JsonNode json, String attribute){ + if(json.get(attribute) == null){ + return "Not specified"; + } else { + return json.get(attribute).asText(); + } + } + public static Result addFingerprint() { Http.Cookie cookie = request().cookies().get("amiunique"); String id; @@ -132,31 +157,34 @@ public static Result addFingerprint() { 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())){ + boolean newFp; + + if(!id.equals("Not supported") && em.checkIfFPExists(id,getAttribute(json,"userAgentHttp"), + getAttribute(json,"acceptHttp"), getAttribute(json,"connectionHttp"), + getAttribute(json,"encodingHttp"), getAttribute(json,"languageHttp"), + 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"))){ fp = em.getExistingFP(id); + newFp = false; } 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(), "", ""); + DigestUtils.sha1Hex(request().remoteAddress()), 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"), "", ""); + newFp = true; } ObjectNode node = (ObjectNode) Json.toJson(fp); @@ -175,27 +203,33 @@ public static Result addFingerprint() { //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 - Integer nbTotal = em.getNumberOfEntries(); + Integer nbTotal = s.getNbTotal(); //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(); + 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()) { + for (Map.Entry entry : osMap.entrySet()) { percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); } - for (Map.Entry entry : browsersMap.entrySet()) { + for (Map.Entry entry : browsersMap.entrySet()) { percentages.put(entry.getKey(),entry.getValue().getCounter()*100/nbTotal); } @@ -211,15 +245,9 @@ public static Result jsRoutes(){ 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)); + 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())); }, 1800)); } 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/ParsedFP.java b/website/app/models/ParsedFP.java index ed847fe..6609c11 100644 --- a/website/app/models/ParsedFP.java +++ b/website/app/models/ParsedFP.java @@ -14,6 +14,7 @@ public class ParsedFP { private String osVersion; private String language; private String timezone; + private String nbFonts; /* Pattern */ private static Pattern langP = Pattern.compile("^(\\S\\S)"); @@ -45,97 +46,103 @@ 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; - } else { - this.os = "Others"; - this.osVersion = "Mobile"; + this.os = "Others"; + this.osVersion = "Desktop"; + } else { + /* 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 + this.browser = "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"; + 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"; + } else { + this.osVersion = "Other distros"; + } + } else {//Others + this.os = "Others"; + if (ua.contains("bot")) { + this.osVersion = "Bot"; + } else { + this.osVersion = "Desktop"; + } } } } @@ -162,11 +169,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 +186,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..6f1f0ed --- /dev/null +++ b/website/app/models/Stats.java @@ -0,0 +1,65 @@ +package models; + +import java.util.HashMap; + +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(); + } + + 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 63% rename from website/app/models/Vermap.java rename to website/app/models/VersionComparator.java index a04321f..4432d24 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); 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/results.scala.html b/website/app/views/results.scala.html index 4606abf..7397aac 100644 --- a/website/app/views/results.scala.html +++ b/website/app/views/results.scala.html @@ -439,9 +439,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 +450,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 0b75000..bf625c1 100644 --- a/website/app/views/stats.scala.html +++ b/website/app/views/stats.scala.html @@ -1,5 +1,5 @@ @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) @@ -134,8 +134,8 @@

Number of entries : @nbTotal

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

Number of entries : @nbTotal

} else { var name = "Not specified"; } - var per = tab[1]*100/nbTotal; + var per = tab*100/nbTotal; if(per>3){ timeArray.push({name:name, y:per}); } else { From 30d03f484d1c260ce3c5b366411bd3f653792c21 Mon Sep 17 00:00:00 2001 From: Benoit Baudry Date: Tue, 16 Dec 2014 10:53:46 +0100 Subject: [PATCH 009/159] update comparison with panopticlick --- website/app/views/faq.scala.html | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/website/app/views/faq.scala.html b/website/app/views/faq.scala.html index 08743f1..2b835ad 100644 --- a/website/app/views/faq.scala.html +++ b/website/app/views/faq.scala.html @@ -142,11 +142,19 @@

How are the fingerprints exploited?

(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 has 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: } diff --git a/website/app/views/main.scala.html b/website/app/views/main.scala.html index 35640b0..74db7ac 100644 --- a/website/app/views/main.scala.html +++ b/website/app/views/main.scala.html @@ -69,7 +69,7 @@ }
  • -   @Messages("menu.timeline") +   @Messages("menu.timeline")   @Messages("menu.new")
  •   @Messages("menu.globalstats") @@ -85,7 +85,7 @@
  • -   @Messages("menu.links") +   @Messages("menu.links")   @Messages("menu.upd")
  •   @Messages("menu.about") diff --git a/website/conf/messages.en b/website/conf/messages.en index 87a06c1..d214750 100644 --- a/website/conf/messages.en +++ b/website/conf/messages.en @@ -26,12 +26,14 @@ home=

    Learn how identifiable you are on the Internet

    \

    What is browser fingerprinting? Learn more

    \

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

    -home.milestone1 = We reached 100,000 fingerprints! We never thought we would reach such a high number! \ - Thanks to all visitors for supporting our research!
    \ - We also launched an AmIUnique extension for +home.milestone1 = We are now over 150,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.pub1 = Our first publication on the current state of browser fingerprinting is finally available!
    \ + You can find the full document +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\ @@ -307,6 +309,8 @@ 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. diff --git a/website/conf/messages.fr b/website/conf/messages.fr index ec7a0a9..fef36da 100644 --- a/website/conf/messages.fr +++ b/website/conf/messages.fr @@ -27,12 +27,14 @@ home=

    Apprenez à quel point votre navigateur est unique

    \

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

    \

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

    -home.milestone1 = Nous avons atteint les 100,000 fingerprints ! Nous n’avions jamais pensé pouvoir atteindre un nombre aussi élevé !
    \ - Merci à tous les visiteurs pour votre contribution à nos recherches !
    \ - Pour les plus curieux, nous avons aussi lancé l’extension AmIUnique pour +home.milestone1 = Nous avons dépassé les 150,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.pub1 = Notre première publication sur l’état actuel du browser fingerprinting est maintenant disponible!
    \ + Vous pouvez trouver l’article complet +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.\ @@ -278,6 +280,8 @@ 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. From a4eb05381a0e680015d0dea6fe2dc4af03c99a4a Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Tue, 15 Mar 2016 17:31:53 +0100 Subject: [PATCH 141/159] Updating homepage messages --- website/conf/messages.en | 3 +-- website/conf/messages.fr | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/website/conf/messages.en b/website/conf/messages.en index d214750..46f8069 100644 --- a/website/conf/messages.en +++ b/website/conf/messages.en @@ -31,8 +31,7 @@ home.milestone1 = We are now over 150,000 fingerprints! Thanks home.milestone2 = and home.milestone3 = . More details can be found home.milestone4= HERE -home.pub1 = Our first publication on the current state of browser fingerprinting is finally available!
    \ - You can find the full document +home.pub1 = Our first paper using the AmIUnique dataset is available home.pub2 = HERE about=

    Who maintains this web site?

    \ diff --git a/website/conf/messages.fr b/website/conf/messages.fr index fef36da..e7b82d4 100644 --- a/website/conf/messages.fr +++ b/website/conf/messages.fr @@ -32,8 +32,7 @@ home.milestone1 = Nous avons dépassé les 150,000 fingerprints home.milestone2 = et home.milestone3 =. Plus de détails home.milestone4 = ICI -home.pub1 = Notre première publication sur l’état actuel du browser fingerprinting est maintenant disponible!
    \ - Vous pouvez trouver l’article complet +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 ?

    \ From 55e5fdc7c4937856874bf1038816e59373d5f476 Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Thu, 21 Apr 2016 11:48:13 +0200 Subject: [PATCH 142/159] Fixing small bug --- website/app/models/VersionComparator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/app/models/VersionComparator.java b/website/app/models/VersionComparator.java index 4432d24..1b63eb8 100644 --- a/website/app/models/VersionComparator.java +++ b/website/app/models/VersionComparator.java @@ -29,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); From 8832d3fd186bcd07a249abd79c48abbdb224465c Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Wed, 22 Jun 2016 13:46:11 +0200 Subject: [PATCH 143/159] Adding attributes from the AudioContext API --- audio.sql | 77 +++++ website/app/controllers/FPController.java | 41 +++ website/app/models/AudioEntity.java | 377 +++++++++++++++++++++ website/app/models/AudioEntityManager.java | 55 +++ website/app/views/fp.scala.html | 5 +- website/public/javascripts/audio.js | 159 +++++++++ website/public/javascripts/sha1.js | 15 + 7 files changed, 728 insertions(+), 1 deletion(-) create mode 100644 audio.sql create mode 100644 website/app/models/AudioEntity.java create mode 100644 website/app/models/AudioEntityManager.java create mode 100644 website/public/javascripts/audio.js create mode 100644 website/public/javascripts/sha1.js 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/website/app/controllers/FPController.java b/website/app/controllers/FPController.java index 0d5d6ff..9df9783 100644 --- a/website/app/controllers/FPController.java +++ b/website/app/controllers/FPController.java @@ -20,6 +20,7 @@ import java.time.temporal.ChronoUnit; import java.util.HashMap; import java.util.Map; +import java.util.Objects; import java.util.UUID; @@ -234,6 +235,45 @@ pluginsJsHashed, getAttribute(json,"platformJs"), getAttribute(json,"cookiesJs") getAttribute(json,"resolutionFlash"), getAttribute(json,"languageFlash"), getAttribute(json,"platformFlash"), getAttribute(json,"adBlock"), pluginsJsHashed, canvasJsHashed, fontsFlashHashed); + //Add audio data + JsonNode audio = json.get("audioData"); + if(Objects.equals(audio.asText(), "Not supported")){ + String n = "Not"; + AudioEntityManager adem = new AudioEntityManager(); + 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 = audioProp.get("ac-sampleRate").asText(); + String acState = audioProp.get("ac-state").asText(); + String acMaxChannelCount = audioProp.get("ac-maxChannelCount").asText(); + String acNumberOfInputs = audioProp.get("ac-numberOfInputs").asText(); + String acNumberOfOutputs = audioProp.get("ac-numberOfOutputs").asText(); + String acChannelCount = audioProp.get("ac-channelCount").asText(); + String acChannelCountMode = audioProp.get("ac-channelCountMode").asText(); + String acChannelInterpretation = audioProp.get("ac-channelInterpretation").asText(); + String anFftSize = audioProp.get("an-fftSize").asText(); + String anFrequencyBinCount = audioProp.get("an-frequencyBinCount").asText(); + String anMinDecibels = audioProp.get("an-minDecibels").asText(); + String anMaxDecibels = audioProp.get("an-maxDecibels").asText(); + String anSmoothingTimeConstant = audioProp.get("an-smoothingTimeConstant").asText(); + String anNumberOfInputs = audioProp.get("an-numberOfInputs").asText(); + String anNumberOfOutputs = audioProp.get("an-numberOfOutputs").asText(); + String anChannelCount = audioProp.get("an-channelCount").asText(); + String anChannelCountMode = audioProp.get("an-channelCountMode").asText(); + String anChannelInterpretation = audioProp.get("an-channelInterpretation").asText(); + String audioDynSum = audio.get("pxi_output").asText(); + String audioDynHash = audio.get("pxi_full_buffer_hash").asText(); + String audioPoints = Json.stringify(audio.get("cc_output")); + String audioDynPoints = Json.stringify(audio.get("hybrid_output")); + + AudioEntityManager adem = new AudioEntityManager(); + 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); + } + newFp = true; } @@ -301,4 +341,5 @@ pluginsJsHashed, getAttribute(json,"platformJs"), getAttribute(json,"cookiesJs") public static Result viewFP() { return ok(viewFP.render(request())); } + } 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/views/fp.scala.html b/website/app/views/fp.scala.html index 2b88f72..efbdaa8 100644 --- a/website/app/views/fp.scala.html +++ b/website/app/views/fp.scala.html @@ -13,6 +13,9 @@ + + + @@ -111,7 +114,7 @@ timezoneJs: timezone, resolutionJs: resolution, localJs: domLocalStorage, sessionJs: domSessionStorage, IEDataJs: ieUserData, canvasJs: canvasData, fontsFlash: fontsFlash, webGLJs: webGLData, vendorWebGLJs: webGLVendor,rendererWebGLJs: webGLRenderer, resolutionFlash: resolutionFlash,languageFlash: languageFlash,platformFlash: platformFlash, - adBlock: document.getElementById('ads')? 'no' : 'yes'}), + adBlock: document.getElementById('ads')? 'no' : 'yes', audioData: audioData}), contentType:"application/json", success: function(res){ 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/sha1.js b/website/public/javascripts/sha1.js new file mode 100644 index 0000000..d0d589f --- /dev/null +++ b/website/public/javascripts/sha1.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)})(); From be6de8b6492f50393990682eab866dc8281a191a Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Mon, 12 Sep 2016 13:03:12 +0200 Subject: [PATCH 144/159] Fixing Safari bug with AudioContext --- website/app/controllers/FPController.java | 66 ++++++++++++++--------- 1 file changed, 40 insertions(+), 26 deletions(-) diff --git a/website/app/controllers/FPController.java b/website/app/controllers/FPController.java index 9df9783..9876781 100644 --- a/website/app/controllers/FPController.java +++ b/website/app/controllers/FPController.java @@ -237,36 +237,50 @@ pluginsJsHashed, getAttribute(json,"platformJs"), getAttribute(json,"cookiesJs") //Add audio data JsonNode audio = json.get("audioData"); + AudioEntityManager adem = new AudioEntityManager(); + String n = "Not"; if(Objects.equals(audio.asText(), "Not supported")){ - String n = "Not"; - AudioEntityManager adem = new AudioEntityManager(); 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 = audioProp.get("ac-sampleRate").asText(); - String acState = audioProp.get("ac-state").asText(); - String acMaxChannelCount = audioProp.get("ac-maxChannelCount").asText(); - String acNumberOfInputs = audioProp.get("ac-numberOfInputs").asText(); - String acNumberOfOutputs = audioProp.get("ac-numberOfOutputs").asText(); - String acChannelCount = audioProp.get("ac-channelCount").asText(); - String acChannelCountMode = audioProp.get("ac-channelCountMode").asText(); - String acChannelInterpretation = audioProp.get("ac-channelInterpretation").asText(); - String anFftSize = audioProp.get("an-fftSize").asText(); - String anFrequencyBinCount = audioProp.get("an-frequencyBinCount").asText(); - String anMinDecibels = audioProp.get("an-minDecibels").asText(); - String anMaxDecibels = audioProp.get("an-maxDecibels").asText(); - String anSmoothingTimeConstant = audioProp.get("an-smoothingTimeConstant").asText(); - String anNumberOfInputs = audioProp.get("an-numberOfInputs").asText(); - String anNumberOfOutputs = audioProp.get("an-numberOfOutputs").asText(); - String anChannelCount = audioProp.get("an-channelCount").asText(); - String anChannelCountMode = audioProp.get("an-channelCountMode").asText(); - String anChannelInterpretation = audioProp.get("an-channelInterpretation").asText(); - String audioDynSum = audio.get("pxi_output").asText(); - String audioDynHash = audio.get("pxi_full_buffer_hash").asText(); - String audioPoints = Json.stringify(audio.get("cc_output")); - String audioDynPoints = Json.stringify(audio.get("hybrid_output")); - - AudioEntityManager adem = new AudioEntityManager(); + 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, From 9314b28f5b88c00087e155e0525397148c868e47 Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Mon, 21 Nov 2016 15:03:01 +0100 Subject: [PATCH 145/159] Fixing some broken links in Links page --- website/app/views/links.scala.html | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/website/app/views/links.scala.html b/website/app/views/links.scala.html index 841e4e5..a844d08 100644 --- a/website/app/views/links.scala.html +++ b/website/app/views/links.scala.html @@ -50,16 +50,23 @@

    Technical presentation

    Scientific papers

    } From 7773022c387faa912f19ae2104823a34b13aca1e Mon Sep 17 00:00:00 2001 From: Pierre Laperdrix Date: Wed, 30 Nov 2016 11:26:28 +0100 Subject: [PATCH 146/159] Fixed typo in one link --- website/app/views/links.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/app/views/links.scala.html b/website/app/views/links.scala.html index a844d08..e4a23a6 100644 --- a/website/app/views/links.scala.html +++ b/website/app/views/links.scala.html @@ -52,7 +52,7 @@

    Scientific papers

  • Hot or Not: Revealing Hidden Services by their Clock Skew (2006)
  • -
  • +
  • How Unique Is Your Web Browser? (2010)
  • From 6fac2e0338d9c63ac275389b2e3a27ccff28e483 Mon Sep 17 00:00:00 2001 From: Alejandro Date: Thu, 4 May 2017 14:39:19 -0500 Subject: [PATCH 147/159] Minor FAQ grammar edit --- website/app/views/faq.scala.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/app/views/faq.scala.html b/website/app/views/faq.scala.html index c0ae47c..b55285a 100644 --- a/website/app/views/faq.scala.html +++ b/website/app/views/faq.scala.html @@ -76,7 +76,7 @@

    How are the fingerprints exploited?

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

    - amiunique has shares some goals with panoticlick, but it provides a number of novelties: + 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
    • From ec1b681392c24102ccda05bcc3286e107355bd9e Mon Sep 17 00:00:00 2001 From: antoinevastel Date: Tue, 23 May 2017 12:47:18 +0200 Subject: [PATCH 148/159] Adds scripts collecting new attributes --- website/public/javascripts/audioSha1.js | 15 + website/public/javascripts/extension.js | 42 +- website/public/javascripts/fpMore.js | 717 ++++++++++++++++++++++++ website/public/javascripts/modernizr.js | 2 + website/public/stylesheets/mqcheck.css | 29 + 5 files changed, 799 insertions(+), 6 deletions(-) create mode 100644 website/public/javascripts/audioSha1.js create mode 100644 website/public/javascripts/fpMore.js create mode 100644 website/public/javascripts/modernizr.js create mode 100644 website/public/stylesheets/mqcheck.css 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/extension.js b/website/public/javascripts/extension.js index 38a3ca7..a65afc5 100644 --- a/website/public/javascripts/extension.js +++ b/website/public/javascripts/extension.js @@ -47,13 +47,43 @@ $(document).ready(function(){ fp.platformFlash = platformFlash; fp.adBlock = document.getElementById('ads')? 'no' : 'yes'; - $.ajax({ - url: 'https://amiunique.org/evolution/'+document.location.hash.substring(1), - data: fp, - contentType: 'application/x-www-form-urlencoded', - method: "POST" - }); + // New attributes + fp.hardwareConcurrency = hardwareConcurrency; + fp.availableScreenResolution = availableScreenResolution; + fp.cpuClass = cpuClass; + fp.modernizr = modernizr; + fp.overwrittenObjects = overwrittenObjects; + fp.appCodeName = appCodeName; + fp.oscpu = oscpu; + fp.appName = appName; + fp.appVersion = appVersion; + fp.languages = languages; + fp.mimeTypes = mimeTypes; + fp.pluginsUsingMimeTypes = pluginsUsingMimeTypes; + fp.product = product; + fp.productSub = productSub; + fp.vendor = vendor; + fp.vendorSub = vendorSub; + fp.touchSupport = touchSupport; + fp.buildID = buildID; + fp.navigatorPrototype = navigatorPrototype; + fp.mathsConstants = mathsConstants; + fp.resOverflow = resOverflow; + fp.errorsGenerated = errorsGenerated; + return Promise.all([p1, p2, p3, p4]).then(function () { + fp.audio = audio.join(";;;"); + fp.unknownImageError = unknownImageError; + fp.fontsEnum = fontsEnum; + fp.osMediaqueries = osMediaqueries; + $.ajax({ + url: 'https://amiunique.org/evolution/'+document.location.hash.substring(1), + // url: 'http://localhost:9000/evolution/dsd25sdfsd', // Needs to add fake param for testing + data: fp, + contentType: 'application/x-www-form-urlencoded', + method: "POST" + }); + }); },2000); }); \ No newline at end of file diff --git a/website/public/javascripts/fpMore.js b/website/public/javascripts/fpMore.js new file mode 100644 index 0000000..f2c95ab --- /dev/null +++ b/website/public/javascripts/fpMore.js @@ -0,0 +1,717 @@ +var UNKNOWN = "unknown"; +var ERROR = "error"; + +var hardwareConcurrency = getHardwareConcurrency(); +var availableScreenResolution = getAvailableScreenResolution(); +var cpuClass = getNavigatorCpuClass(); +var modernizr = testModernizr(); +var overwrittenObjects = testOverwrittenObjects(); +var appCodeName = getAppCodeName(); +var oscpu = getOscpu(); +var appName = getAppName(); +var appVersion = getAppVersion(); +var languages = getLanguages(); +var mimeTypes = getMimeTypes(); +var pluginsUsingMimeTypes = getPluginsUsingMimeTypes(); +var product = getProduct(); +var productSub = getProductSub(); +var vendor = getVendor(); +var vendorSub = getVendorSub(); +var touchSupport = getTouchSupport(); +var buildID = getBuildId(); +var navigatorPrototype = getNavigatorPrototype(); +var mathsConstants = getMathsConstants(); +var resOverflow = generateStackOverflow(); +var errorsGenerated = generateErrors(); + +// Async part +// TODO see how we manage async here? with promises? +//Unknown image +var unknownImageError = ""; +var p1 = new Promise(function(resolve, reject){ + generateUnknownImageError().then(function(val){ + unknownImageError = val; + return resolve(); + }); +}); + +//font enum +var fontsEnum = ""; +var p2 = new Promise(function(resolve, reject){ + getFontsEnum().then(function(val){ + fontsEnum = val; + return resolve(); + }) +}); + +var audio = ""; +var p3 = new Promise(function(resolve, reject){ + getAudio().then(function(val){ + audio = val.data; + return resolve(); + }); +}); + +var osMediaqueries = ""; +var p4 = new Promise(function(resolve, reject){ + getOSMq().then(function(val){ + osMediaqueries = val; + return resolve(); + }); +}); + +function getHardwareConcurrency(){ + if(navigator.hardwareConcurrency){ + return navigator.hardwareConcurrency; + } + return UNKNOWN; +} + +function getAvailableScreenResolution(){ + if(screen.availWidth && screen.availHeight) { + return screen.availWidth+","+screen.availHeight; + } + return UNKNOWN; +} + +function getNavigatorCpuClass(){ + if(navigator.cpuClass){ + return navigator.cpuClass; + } + return UNKNOWN; +} + +function testModernizr(){ + var propertiesVec = []; + var modernizrProperties = Object.getOwnPropertyNames(Modernizr); + modernizrProperties.forEach(function(prop){ + if(typeof Modernizr[prop] == "boolean"){ + propertiesVec.push(prop+"-"+Modernizr[prop].toString()); + } + }); + return propertiesVec.join(";"); +} + +function getOscpu(){ + if(navigator.oscpu){ + return navigator.oscpu; + } + return UNKNOWN; +} + +function testOverwrittenObjects(){ + var screenTest; + try{ + screenTest = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(screen), "width").get.toString(); + } catch(e){ + screenTest = ERROR; + } + var canvasTest; + try{ + canvasTest = Object.getOwnPropertyDescriptor(window.HTMLCanvasElement.prototype, "toDataURL").value.toString(); + } catch(e){ + canvasTest = ERROR; + } + + var dateTest; + try{ + dateTest = Object.getOwnPropertyDescriptor(Date.prototype, "getTimezoneOffset").value.toString(); + } catch(e) { + dateTest = ERROR; + } + + // We separe with weird characters in case they use dash in their overwritten functions + return screenTest+"~~~"+canvasTest+"~~~"+dateTest; +} + +function getOSMq(){ + return new Promise(function(resolve, reject){ + document.addEventListener("DOMContentLoaded", function(event){ + var divTest = document.createElement("div"); + var body = document.getElementsByTagName("body")[0]; + body.appendChild(divTest); + + var macP = document.createElement("p"); + macP.setAttribute("id", "testmac1"); + var winxpP = document.createElement("p"); + winxpP.setAttribute("id", "testwinxp"); + var winvisP = document.createElement("p"); + winvisP.setAttribute("id", "testwinvis"); + var win7P = document.createElement("p"); + win7P.setAttribute("id", "testwin7"); + var win8P = document.createElement("p"); + win8P.setAttribute("id", "testwin8"); + + divTest.appendChild(macP); + divTest.appendChild(winxpP); + divTest.appendChild(winvisP); + divTest.appendChild(win7P); + divTest.appendChild(win8P); + + var queryMatchedColor = "red"; + var res = []; + + if(macP.style.color == queryMatchedColor){ + res.push("true"); + }else{ + res.push("false"); + } + + if(winxpP.style.color == queryMatchedColor){ + res.push("true"); + }else{ + res.push("false"); + } + + if(winvisP.style.color == queryMatchedColor){ + res.push("true"); + }else{ + res.push("false"); + } + + if(win7P.style.color == queryMatchedColor){ + res.push("true"); + }else{ + res.push("false"); + } + + if(win8P.style.color == queryMatchedColor){ + res.push("true"); + }else{ + res.push("false"); + } + + return resolve(res.join(";")); + }); + }); +} + +function getAppName(){ + return navigator.appName; +} + +function getAppCodeName(){ + return navigator.appCodeName; +} + +function getAppVersion(){ + return navigator.appVersion; +} + +function getLanguages(){ + if(navigator.languages){ + return navigator.languages.join("~~"); + } + return UNKNOWN; +} + +function getMimeTypes(){ + var mimeTypes = []; + for(var i = 0; i < navigator.mimeTypes.length; i++){ + var mt = navigator.mimeTypes[i]; + mimeTypes.push([mt.description, mt.type, mt.suffixes].join("~")); + } + return mimeTypes.join(";;"); +} + +function getPluginsUsingMimeTypes(){ + var plugins = []; + for(var i = 0; i < navigator.mimeTypes.length; i++){ + var mt = navigator.mimeTypes[i]; + plugins.push([mt.enabledPlugin.name, mt.enabledPlugin.description, mt.enabledPlugin.filename].join("::")+mt.type); + } + return plugins.join(";;"); +} + +function getProduct(){ + return navigator.product; +} + +function getProductSub(){ + return navigator.productSub; +} + +function getVendor(){ + return navigator.vendor; +} + +function getVendorSub(){ + return navigator.vendorSub; +} + +function getTouchSupport(){ + var maxTouchPoints = 0; + var touchEvent = false; + if(typeof navigator.maxTouchPoints !== "undefined") { + maxTouchPoints = navigator.maxTouchPoints; + } else if (typeof navigator.msMaxTouchPoints !== "undefined") { + maxTouchPoints = navigator.msMaxTouchPoints; + } + try { + document.createEvent("TouchEvent"); + touchEvent = true; + } catch(_) { /* squelch */ } + var touchStart = "ontouchstart" in window; + return [maxTouchPoints, touchEvent, touchStart].join(";"); +} + +function getBuildId(){ + if(navigator.buildID){ + return navigator.buildID; + } + return UNKNOWN; +} + +function getNavigatorPrototype(){ + var obj = window.navigator; + var protoNavigator = []; + do Object.getOwnPropertyNames(obj).forEach(function(name) { + protoNavigator.push(name); + }); + while(obj = Object.getPrototypeOf(obj)); + + var res; + var finalProto = []; + protoNavigator.forEach(function(prop){ + var objDesc = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(navigator), prop); + if(objDesc != undefined){ + if(objDesc.value != undefined){ + res = objDesc.value.toString(); + }else if(objDesc.get != undefined){ + res = objDesc.get.toString(); + } + }else{ + res = ""; + } + finalProto.push(prop+"~~~"+res); + + }); + return finalProto.join(";;;"); +} + +function getMathsConstants(){ + function asinh(x) { + if (x === -Infinity) { + return x; + } else { + return Math.log(x + Math.sqrt(x * x + 1)); + } + } + + function acosh(x) { + return Math.log(x + Math.sqrt(x * x - 1)); + } + + function atanh(x) { + return Math.log((1 + x) / (1 - x)) / 2; + } + + function cbrt(x) { + var y = Math.pow(Math.abs(x), 1 / 3); + return x < 0 ? -y : y; + } + + function cosh(x) { + var y = Math.exp(x); + return (y + 1 / y) / 2; + } + + function expm1(x) { + return Math.exp(x) - 1; + } + + function log1p(x) { + return Math.log(1 + x); + } + + function sinh(x) { + var y = Math.exp(x); + return (y - 1 / y) / 2; + } + + function tanh(x) { + if (x === Infinity) { + return 1; + } else if (x === -Infinity) { + return -1; + } else { + var y = Math.exp(2 * x); + return (y - 1) / (y + 1); + } + } + + return [ + asinh(1), + (acosh(1e300) == "Infinity") ? "Infinity" : acosh(1e300), + atanh(0.5), + expm1(1), + cbrt(100), + log1p(10), + sinh(1), + cosh(10), + tanh(1) + ].join(";"); +} + +function generateStackOverflow(){ + var depth = 0; + var errorMessage; + var errorName; + function inc(){ + try{ + depth++; + inc(); + }catch(e){ + errorMessage = e.message; + errorName = e.name; + } + } + + inc(); + return [depth, errorName, errorMessage].join(";;;"); +} + +function generateWebSocketError(){ + var error = ""; + try{ + var a = new WebSocket("itsgonnafail"); + }catch(e){ + error += e.toString(); + } + return error; +} + +function generateErrors(){ + var errors = []; + try{ + azeaze+3; + }catch(e){ + errors.push(e.message); + errors.push(e.fileName); + errors.push(e.lineNumber); + errors.push(e.description); + errors.push(e.number); + errors.push(e.columnNumber); + try{ + errors.push(e.toSource().toString()); + }catch(e){ + errors.push(undefined); + } + } + + try{ + var a = new WebSocket("itsgonnafail"); + }catch(e){ + errors.push(e.toString()); + } + + return errors.join("~~~"); +} + + +// Async methods below + +function generateUnknownImageError(){ + return new Promise(function(resolve, reject){ + document.addEventListener("DOMContentLoaded", function(event){ + var body = document.getElementsByTagName("body")[0]; + var image = document.createElement("img"); + image.src = "http://iloveponeydotcom32188.jg"; + image.setAttribute("id", "fakeimage"); + body.appendChild(image); + image = document.getElementById("fakeimage"); + setTimeout(function(){ + resolve([image.width, image.height].join(";")); + }, 500); + }); + }); +} + +function getAudio() { + var audioData = []; + + // Performs fingerprint as found in https://client.a.pxi.pub/PXmssU3ZQ0/main.min.js + //Sum of buffer values + var p1 = new Promise(function (resolve, reject) { + try { + if (context = new (window.OfflineAudioContext || window.webkitOfflineAudioContext)(1, 44100, 44100), !context) { + audioData.push(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) { + try { + audioData.push(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.push(hash.toString(CryptoJS.enc.Hex)); + var tmp = []; + for (var i = 4500; 5e3 > i; i++) { + tmp.push(Math.abs(evnt.renderedBuffer.getChannelData(0)[i])); + } + pxi_compressor.disconnect(); + audioData.push(tmp.join("~")); + resolve(); + } catch(u){ + audioData.push("0"); + resolve(); + } + } + } catch (u) { + audioData.push(0); + resolve(); + } + }); + +// 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 + } + + var p2 = new Promise(function (resolve, reject) { + try { + var nt_vc_context = window.AudioContext || window.webkitAudioContext; + if ("function" !== typeof nt_vc_context) audioData.push("Not available"); + else { + var f = new nt_vc_context, + d = f.createAnalyser(); + var tmp = a({}, f, "ac-"); + tmp = a(tmp, f.destination, "ac-"); + tmp = a(tmp, f.listener, "ac-"); + var res = a(tmp, d, "an-"); + var arr = [], i; + for(i in res){ + if(res.hasOwnProperty(i)){ + arr.push(res[i]); + } + } + arr.sort(function(x,b){ return x[0]>b[0]?1:-1; }) + audioData.push(arr.join("~")); + } + } catch (g) { + audioData.push(0) + } + resolve(); + }); + +// Performs fingerprint as found in https://www.cdn-net.com/cc.js + var cc_output = []; + + var p3 = new Promise(function (resolve, reject) { + 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.push(cc_output.slice(0, 30).join("~")); + resolve(); + }; + + oscillator.start(0); + }); + +// Performs a hybrid of cc/pxi methods found above + var hybrid_output = []; + + var p4 = new Promise(function (resolve, reject) { + 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.push(hybrid_output.slice(0, 30).join("~")); + resolve(); + }; + + oscillator.start(0); + }); + + return Promise.all([p1, p2, p3, p4]).then(function () { + return {name: "audio", data: audioData}; + }); +} + +function getFontsEnum(){ + // Code taken from fingerprintjs2 + var fontsToTest = "cursive;monospace;serif;sans-serif;fantasy;default;Arial;Arial Black;Arial Narrow;Arial Rounded MT Bold;Book Antiqua;Bookman Old Style;Bradley Hand ITC;Bodoni MT;Calibri;Century;Century Gothic;Casual;Comic Sans MS;Consolas;Copperplate Gothic Bold;Courier;Courier New;English Text MT;Felix Titling;Futura;Garamond;Geneva;Georgia;Gentium;Haettenschweiler;Helvetica;Impact;Jokerman;King;Kootenay;Latha;Liberation Serif;Lucida Console;Lalit;Lucida Grande;Magneto;Mistral;Modena;Monotype Corsiva;MV Boli;OCR A Extended;Onyx;Palatino Linotype;Papyrus;Parchment;Pericles;Playbill;Segoe Print;Shruti;Tahoma;TeX;Times;Times New Roman;Trebuchet MS;Verdana;Verona;Arial Cyr;Comic Sans MS;Arial Black;Chiller;Arial Narrow;Arial Rounded MT Bold;Baskerville Old Face;Berlin Sans FB;Blackadder ITC;Lucida Console;Symbol;Times New Roman;Webdings;Agency FB;Vijaya;Algerian;Arial Unicode MS;Bodoni MT Poster Compressed;Bookshelf Symbol 7;Calibri;Cambria;Cambria Math;Kartika;MS Mincho;MS Outlook;MT Extra;Segoe UI;Aharoni;Aparajita;Amienne;cursive;Academy Engraved LET;LCD;LuzSans-Book;sans-serif;ZWAdobeF;Eurostile;SimSun-PUA;Blackletter686 BT;Myriad Web Pro Condensed;Matisse ITC;Bell Gothic Std Black;David Transparent;Adobe Caslon Pro;AR BERKLEY;Australian Sunrise;Myriad Web Pro;Gentium Basic;Highlight LET;Adobe Myungjo Std M;GothicE;HP PSG;DejaVu Sans;Arno Pro;Futura Bk;DejaVu Sans Condensed;Euro Sign;Neurochrome;Bell Gothic Std Light;Jokerman Alts LET;Adobe Fan Heiti Std B;Baby Kruffy;Tubular;Woodcut;HGHeiseiKakugothictaiW3;YD2002;Tahoma Small Cap;Helsinki;Bickley Script;Unicorn;X-Files;GENISO;Frutiger SAIN Bd v.1;Opus;ZDingbats;ABSALOM;Vagabond;Year supply of fairy cakes;Myriad Condensed Web;Segoe Media Center;Coronet;Helsinki Metronome;Segoe Condensed;Weltron Urban;AcadEref;DecoType Naskh;Freehand521 BT;Opus Chords Sans;Enviro;SWGamekeys MT;Croobie;Arial Narrow Special G1;AVGmdBU;Candles;Futura Bk BT;Andy;QuickType;WP Arabic Sihafa;DigifaceWide;ELEGANCE;BRAZIL;Pepita MT;Nina;Geneva;OCR B MT;Futura;Blade Runner Movie Font;Allegro BT;Lucida Blackletter;AGA Arabesque;AdLib BT;Clarendon;Monotype Sorts;Alibi;Bremen Bd BT;mono;News Gothic MT;AvantGarde Bk BT;chs_boot;fantasy;Palatino;BernhardFashion BT;Courier New;CloisterBlack BT;Scriptina;Tahoma;BernhardMod BT;Virtual DJ;Nokia Smiley;Boulder;Andale Mono IPA;Belwe Lt BT;Calligrapher;Belwe Cn BT;Tanseek Pro Arabic;FuturaBlack BT;Abadi MT Condensed;Mangal;Chaucer;Belwe Bd BT;Liberation Serif;DomCasual BT;Bitstream Vera Sans;URW Gothic L;GeoSlab703 Lt BT;Bitstream Vera Sans Mono;Nimbus Mono L;Heather;Antique Olive;Clarendon Cn BT;Amazone BT;Bitstream Vera Serif;Utopia;Americana BT;Map Symbols;Bitstream Charter;Aurora Cn BT;CG Omega;Lohit Punjabi;Balloon XBd BT;Akhbar MT;Courier 10 Pitch;Benguiat Bk BT;Market;Cursor;Bodoni Bk BT;Letter Gothic;Luxi Sans;Brush455 BT;Sydnie;Lohit Hindi;Lithograph;Albertus;DejaVu LGC Serif;Lydian BT;Antique Olive Compact;KacstArt;Incised901 Bd BT;Clarendon Extended;Lohit Telugu;Incised901 Lt BT;GiovanniITCTT;KacstOneFixed;Folio XBd BT;Edda;Loma;Formal436 BT;Fine Hand;Garuda;Impress BT;RefSpecialty;Sazanami Mincho;Staccato555 BT;VL Gothic;Hkmer OS;WP BoxDrawing;Clarendon Blk BT;Droid Sans;CommonBullets;Sherwood;Helvetica;CopprplGoth Bd BT;Smudger Alts LET;BPG Rioni;CopprplGoth BT;Guitar Pro 5;Estrangelo TurAbdin;Dauphin;Arial Tur;English111 Vivace BT;Steamer;OzHandicraft BT;Futura Lt BT;Liberation Sans Narrow;Futura XBlk BT;Candy Round BTN Cond;GoudyHandtooled BT;GrilledCheese BTN Cn;GoudyOlSt BT;Galeforce BTN;Kabel Bk BT;Sneakerhead BTN Shadow;OCR-A BT;Denmark;OCR-B 10 BT;Swiss921 BT;PosterBodoni BT;Arial (Arabic);Serifa BT;FlemishScript BT;Arial;American Typewriter;Arial Black;Apple Symbols;Arial Narrow;AppleMyungjo;Arial Rounded MT Bold;Zapfino;Arial Unicode MS;BlairMdITC TT-Medium;Century Gothic;Cracked;Papyrus;KufiStandardGK;Plantagenet Cherokee;Courier;Helvetica;Baskerville Old Face;Apple Casual;Type Embellishments One LET;Bookshelf Symbol 7;Abadi MT Condensed Extra Bold;Calibri;Calibri Bold;Calisto MT;Chalkduster;Cambria;Franklin Gothic Book Italic;Century;Geneva CY;Franklin Gothic Book;Helvetica Light;Gill Sans MT;Academy Engraved LET;MT Extra;Bank Gothic;Eurostile;Bodoni SvtyTwo SC ITC TT-Book;Tekton Pro;Courier CE;Maestro;BO Futura BoldOblique;Lucida Bright Demibold;New;AGaramond;Charcoal;DIN-Black;Lucida Sans Demibold;Stone Sans OS ITC TT-Bold;AGaramond Italic;Bickham Script Pro Regular;Adobe Arabic Bold;AGaramond Semibold;Al Bayan Bold;Doremi;AGaramond SemiboldItalic;Arno Pro Bold;Casual;B Futura Bold;Frutiger 47LightCn;Gadget;HelveticaNeueLT Std Bold;Frutiger 57Cn;DejaVu Serif Italic Condensed;Myriad Pro Black It;Frutiger 67BoldCn;Gentium Basic Bold;Sand;GillSans;H Futura Heavy;Liberation Mono Bold;GillSans Bold;Cambria Math;Courier Final Draft;HelveticaNeue BlackCond;cursive;Techno;HelveticaNeue BlackCondObl;Gabriola;JazzText Extended;HelveticaNeue BlackExt;sans-serif;Textile;HelveticaNeue BlackExtObl fantasy;HelveticaNeue BoldCond;Palatino Linotype Bold;HelveticaNeue BoldCondObl;BIRTH OF A HERO;HelveticaNeue BoldExt;Bleeding Cowboys;HelveticaNeue BoldExtObl;ChopinScript;HelveticaNeue ExtBlackCond;LCD;HelveticaNeue ExtBlackCondObl;Myriad Web Pro Condensed;HelveticaNeue HeavyCond;Scriptina;HelveticaNeue HeavyCondObl;OpenSymbol;HelveticaNeue HeavyExt;Virtual DJ;HelveticaNeue HeavyExtObl;Guitar Pro 5;HelveticaNeue LightCondObl;Nueva Std;HelveticaNeue ThinCond;Chicago;HelveticaNeue ThinCondObl;Nueva Std Bold;Brush Script MT;Capitals;Myriad Web Pro;Avant Garde;B Avant Garde Demi;Nueva Std Bold Italic;BI Avant Garde DemiOblique;MaestroTimes;Univers BoldExtObl;APC Courier;Myriad Web Pro Bold;Liberation Serif;Myriad Pro Light;Carta;DIN-Bold;DIN-Light;Myriad Web Pro Condensed Italic;DIN-Medium;Tekton Pro Oblique;DIN-Regular;AScore;HelveticaNeue UltraLigCondObl;Opus;HelveticaNeue UltraLigExt;Myriad Pro Light It;HelveticaNeue UltraLigExtObl;Opus Chords Sans;HO Futura HeavyOblique;Opus Japanese Chords;L Frutiger Light;VT100;L Futura Light;Helsinki;LO Futura LightOblique;Helsinki Metronome;Myriad Pro Black;New York;O Futura BookOblique;R Frutiger Roman;Reprise;TradeGothic;Warnock Pro Bold Caption;Univers 45 Light;Warnock Pro;XBO Futura ExtraBoldOblique;Univers 45 LightOblique;Liberation Mono;Univers 55 Oblique;UC LCD;Univers 57 Condensed;Warnock Pro Bold;Univers ExtraBlack;Warnock Pro Light Ital Subhead;Univers LightUltraCondensed;Matrix Ticker;Univers UltraCondensed;Fang Song".split(";"); + + return new Promise(function(resolve, reject){ + document.addEventListener("DOMContentLoaded", function(event){ + var baseFonts = ["monospace", "sans-serif", "serif"]; + var testString = "mmmmmmmmmmlli"; + var testSize = "72px"; + var h = document.getElementsByTagName("body")[0]; + + // div to load spans for the base fonts + var baseFontsDiv = document.createElement("div"); + + // div to load spans for the fonts to detect + var fontsDiv = document.createElement("div"); + + var defaultWidth = {}; + var defaultHeight = {}; + + // creates a span where the fonts will be loaded + var createSpan = function() { + var s = document.createElement("span"); + /* + * We need this css as in some weird browser this + * span elements shows up for a microSec which creates a + * bad user experience + */ + s.style.position = "absolute"; + s.style.left = "-9999px"; + s.style.fontSize = testSize; + s.style.lineHeight = "normal"; + s.innerHTML = testString; + return s; + }; + + var createSpanWithFonts = function(fontToDetect, baseFont) { + var s = createSpan(); + s.style.fontFamily = "'" + fontToDetect + "'," + baseFont; + return s; + }; + + var initializeBaseFontsSpans = function() { + var spans = []; + for (var index = 0, length = baseFonts.length; index < length; index++) { + var s = createSpan(); + s.style.fontFamily = baseFonts[index]; + baseFontsDiv.appendChild(s); + spans.push(s); + } + return spans; + }; + + var initializeFontsSpans = function() { + var spans = {}; + for(var i = 0, l = fontsToTest.length; i < l; i++) { + var fontSpans = []; + for(var j = 0, numDefaultFonts = baseFonts.length; j < numDefaultFonts; j++) { + var s = createSpanWithFonts(fontsToTest[i], baseFonts[j]); + fontsDiv.appendChild(s); + fontSpans.push(s); + } + spans[fontsToTest[i]] = fontSpans; // Stores {fontName : [spans for that font]} + } + return spans; + }; + + var isFontAvailable = function(fontSpans) { + var detected = false; + for(var i = 0; i < baseFonts.length; i++) { + detected = (fontSpans[i].offsetWidth !== defaultWidth[baseFonts[i]] || fontSpans[i].offsetHeight !== defaultHeight[baseFonts[i]]); + if(detected) { + return detected; + } + } + return detected; + }; + + var baseFontsSpans = initializeBaseFontsSpans(); + + // add the spans to the DOM + h.appendChild(baseFontsDiv); + + // get the default width for the three base fonts + for (var index = 0, length = baseFonts.length; index < length; index++) { + defaultWidth[baseFonts[index]] = baseFontsSpans[index].offsetWidth; // width for the default font + defaultHeight[baseFonts[index]] = baseFontsSpans[index].offsetHeight; // height for the default font + } + + // create spans for fonts to detect + var fontsSpans = initializeFontsSpans(); + + // add all the spans to the DOM + h.appendChild(fontsDiv); + + // check available fonts + var available = []; + for(var i = 0, l = fontsToTest.length; i < l; i++) { + if(isFontAvailable(fontsSpans[fontsToTest[i]])) { + available.push(fontsToTest[i]+"--true"); + }else{ + available.push(fontsToTest[i]+"--false"); + } + } + + // remove spans from DOM + h.removeChild(fontsDiv); + h.removeChild(baseFontsDiv); + return resolve(available.join(";;")); + }); + }); +} \ No newline at end of file diff --git a/website/public/javascripts/modernizr.js b/website/public/javascripts/modernizr.js new file mode 100644 index 0000000..3f3884b --- /dev/null +++ b/website/public/javascripts/modernizr.js @@ -0,0 +1,2 @@ +/* modernizr-custom.js */ +!function(window,document,undefined){function is(e,t){return typeof e===t}function testRunner(){var e,t,r,n,i,o,d;for(var a in tests)if(tests.hasOwnProperty(a)){if(e=[],t=tests[a],t.name&&(e.push(t.name.toLowerCase()),t.options&&t.options.aliases&&t.options.aliases.length))for(r=0;ra;a++)if(l=e[a],c=mStyle.style[l],contains(l,"-")&&(l=cssToDOM(l)),mStyle.style[l]!==undefined){if(n||is(r,"undefined"))return i(),"pfx"==t?l:!0;try{mStyle.style[l]=r}catch(p){}if(mStyle.style[l]!=c)return i(),"pfx"==t?l:!0}return i(),!1}function testPropsAll(e,t,r,n,i){var o=e.charAt(0).toUpperCase()+e.slice(1),d=(e+" "+cssomPrefixes.join(o+" ")+o).split(" ");return is(t,"string")||is(t,"undefined")?testProps(d,t,n,i):(d=(e+" "+domPrefixes.join(o+" ")+o).split(" "),testDOMProps(d,t,r))}function testAllProps(e,t,r){return testPropsAll(e,undefined,undefined,t,r)}var tests=[],ModernizrProto={_version:"3.3.1",_config:{classPrefix:"",enableClasses:!0,enableJSClass:!0,usePrefixes:!0},_q:[],on:function(e,t){var r=this;setTimeout(function(){t(r[e])},0)},addTest:function(e,t,r){tests.push({name:e,fn:t,options:r})},addAsyncTest:function(e){tests.push({name:null,fn:e})}},Modernizr=function(){};Modernizr.prototype=ModernizrProto,Modernizr=new Modernizr,Modernizr.addTest('beacon','sendBeacon' in navigator),Modernizr.addTest("applicationcache","applicationCache"in window),Modernizr.addTest("blobconstructor",function(){try{return!!new Blob}catch(e){return!1}},{aliases:["blob-constructor"]}),Modernizr.addTest("cookies",function(){try{document.cookie="cookietest=1";var e=-1!=document.cookie.indexOf("cookietest=");return document.cookie="cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT",e}catch(t){return!1}}),Modernizr.addTest("cors","XMLHttpRequest"in window&&"withCredentials"in new XMLHttpRequest),Modernizr.addTest("customprotocolhandler",function(){if(!navigator.registerProtocolHandler)return!1;try{navigator.registerProtocolHandler("thisShouldFail")}catch(e){return e instanceof TypeError}return!1}),Modernizr.addTest("customevent","CustomEvent"in window&&"function"==typeof window.CustomEvent),Modernizr.addTest("dataview","undefined"!=typeof DataView&&"getFloat64"in DataView.prototype),Modernizr.addTest("eventlistener","addEventListener"in window),Modernizr.addTest("geolocation","geolocation"in navigator),Modernizr.addTest("history",function(){var e=navigator.userAgent;return-1===e.indexOf("Android 2.")&&-1===e.indexOf("Android 4.0")||-1===e.indexOf("Mobile Safari")||-1!==e.indexOf("Chrome")||-1!==e.indexOf("Windows Phone")?window.history&&"pushState"in window.history:!1}),Modernizr.addTest("ie8compat",!window.addEventListener&&!!document.documentMode&&7===document.documentMode),Modernizr.addTest("json","JSON"in window&&"parse"in JSON&&"stringify"in JSON),Modernizr.addTest("notification",function(){if(!window.Notification||!window.Notification.requestPermission)return!1;if("granted"===window.Notification.permission)return!0;try{new window.Notification("")}catch(e){if("TypeError"===e.name)return!1}return!0}),Modernizr.addTest("postmessage","postMessage"in window),Modernizr.addTest("queryselector","querySelector"in document&&"querySelectorAll"in document),Modernizr.addTest("serviceworker","serviceWorker"in navigator),Modernizr.addTest("svg",!!document.createElementNS&&!!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect),Modernizr.addTest("templatestrings",function(){var supports;try{eval("``"),supports=!0}catch(e){}return!!supports}),Modernizr.addTest("typedarrays","ArrayBuffer"in window);var supports=!1;try{supports="WebSocket"in window&&2===window.WebSocket.CLOSING}catch(e){}Modernizr.addTest("websockets",supports),Modernizr.addTest("xdomainrequest","XDomainRequest"in window),Modernizr.addTest("webaudio",function(){var e="webkitAudioContext"in window,t="AudioContext"in window;return Modernizr._config.usePrefixes?e||t:t});var CSS=window.CSS;Modernizr.addTest("cssescape",CSS?"function"==typeof CSS.escape:!1);var newSyntax="CSS"in window&&"supports"in window.CSS,oldSyntax="supportsCSS"in window;Modernizr.addTest("supports",newSyntax||oldSyntax),Modernizr.addTest("target",function(){var e=window.document;if(!("querySelectorAll"in e))return!1;try{return e.querySelectorAll(":target"),!0}catch(t){return!1}}),Modernizr.addTest("microdata","getItems"in document),Modernizr.addTest("mutationobserver",!!window.MutationObserver||!!window.WebKitMutationObserver),Modernizr.addTest("picture","HTMLPictureElement"in window),Modernizr.addTest("es5array",function(){return!!(Array.prototype&&Array.prototype.every&&Array.prototype.filter&&Array.prototype.forEach&&Array.prototype.indexOf&&Array.prototype.lastIndexOf&&Array.prototype.map&&Array.prototype.some&&Array.prototype.reduce&&Array.prototype.reduceRight&&Array.isArray)}),Modernizr.addTest("es5date",function(){var e="2013-04-12T06:06:37.307Z",t=!1;try{t=!!Date.parse(e)}catch(r){}return!!(Date.now&&Date.prototype&&Date.prototype.toISOString&&Date.prototype.toJSON&&t)}),Modernizr.addTest("es5function",function(){return!(!Function.prototype||!Function.prototype.bind)}),Modernizr.addTest("es5object",function(){return!!(Object.keys&&Object.create&&Object.getPrototypeOf&&Object.getOwnPropertyNames&&Object.isSealed&&Object.isFrozen&&Object.isExtensible&&Object.getOwnPropertyDescriptor&&Object.defineProperty&&Object.defineProperties&&Object.seal&&Object.freeze&&Object.preventExtensions)}),Modernizr.addTest("strictmode",function(){"use strict";return!this}()),Modernizr.addTest("es5string",function(){return!(!String.prototype||!String.prototype.trim)}),Modernizr.addTest("es5syntax",function(){var value,obj,stringAccess,getter,setter,reservedWords,zeroWidthChars;try{return stringAccess=eval('"foobar"[3] === "b"'),getter=eval("({ get x(){ return 1 } }).x === 1"),eval("({ set x(v){ value = v; } }).x = 1"),setter=1===value,eval("obj = ({ if: 1 })"),reservedWords=1===obj["if"],zeroWidthChars=eval("_‌‍ = true"),stringAccess&&getter&&setter&&reservedWords&&zeroWidthChars}catch(ignore){return!1}}),Modernizr.addTest("es5undefined",function(){var e,t;try{t=window.undefined,window.undefined=12345,e="undefined"==typeof window.undefined,window.undefined=t}catch(r){return!1}return e}),Modernizr.addTest("es5",function(){return!!(Modernizr.es5array&&Modernizr.es5date&&Modernizr.es5function&&Modernizr.es5object&&Modernizr.strictmode&&Modernizr.es5string&&Modernizr.json&&Modernizr.es5syntax&&Modernizr.es5undefined)}),Modernizr.addTest("es6array",!!(Array.prototype&&Array.prototype.copyWithin&&Array.prototype.fill&&Array.prototype.find&&Array.prototype.findIndex&&Array.prototype.keys&&Array.prototype.entries&&Array.prototype.values&&Array.from&&Array.of)),Modernizr.addTest("es6collections",!!(window.Map&&window.Set&&window.WeakMap&&window.WeakSet)),Modernizr.addTest("generators",function(){try{new Function("function* test() {}")()}catch(e){return!1}return!0}),Modernizr.addTest("es6math",!!(Math&&Math.clz32&&Math.cbrt&&Math.imul&&Math.sign&&Math.log10&&Math.log2&&Math.log1p&&Math.expm1&&Math.cosh&&Math.sinh&&Math.tanh&&Math.acosh&&Math.asinh&&Math.atanh&&Math.hypot&&Math.trunc&&Math.fround)),Modernizr.addTest("es6number",!!(Number.isFinite&&Number.isInteger&&Number.isSafeInteger&&Number.isNaN&&Number.parseInt&&Number.parseFloat&&Number.isInteger(Number.MAX_SAFE_INTEGER)&&Number.isInteger(Number.MIN_SAFE_INTEGER)&&Number.isFinite(Number.EPSILON))),Modernizr.addTest("es6object",!!(Object.assign&&Object.is&&Object.setPrototypeOf)),Modernizr.addTest("promises",function(){return"Promise"in window&&"resolve"in window.Promise&&"reject"in window.Promise&&"all"in window.Promise&&"race"in window.Promise&&function(){var e;return new window.Promise(function(t){e=t}),"function"==typeof e}()}),Modernizr.addTest("es6string",!!(String.fromCodePoint&&String.raw&&String.prototype.codePointAt&&String.prototype.repeat&&String.prototype.startsWith&&String.prototype.endsWith&&String.prototype.contains)),Modernizr.addTest("devicemotion","DeviceMotionEvent"in window),Modernizr.addTest("deviceorientation","DeviceOrientationEvent"in window),Modernizr.addTest("filereader",!!(window.File&&window.FileList&&window.FileReader)),Modernizr.addTest("lowbandwidth",function(){var e=navigator.connection||{type:0};return 3==e.type||4==e.type||/^[23]g$/.test(e.type)}),Modernizr.addTest("eventsource","EventSource"in window),Modernizr.addTest("fetch","fetch"in window),Modernizr.addTest("xhrresponsetype",function(){if("undefined"==typeof XMLHttpRequest)return!1;var e=new XMLHttpRequest;return e.open("get","/",!0),"response"in e}()),Modernizr.addTest("xhr2","XMLHttpRequest"in window&&"withCredentials"in new XMLHttpRequest),Modernizr.addTest("speechsynthesis","SpeechSynthesisUtterance"in window),Modernizr.addTest("localstorage",function(){var e="modernizr";try{return localStorage.setItem(e,e),localStorage.removeItem(e),!0}catch(t){return!1}}),Modernizr.addTest("sessionstorage",function(){var e="modernizr";try{return sessionStorage.setItem(e,e),sessionStorage.removeItem(e),!0}catch(t){return!1}}),Modernizr.addTest("websqldatabase","openDatabase"in window),Modernizr.addTest("svgfilters",function(){var e=!1;try{e="SVGFEColorMatrixElement"in window&&2==SVGFEColorMatrixElement.SVG_FECOLORMATRIX_TYPE_SATURATE}catch(t){}return e}),Modernizr.addTest("urlparser",function(){var e;try{return e=new URL("http://modernizr.com/"),"http://modernizr.com/"===e.href}catch(t){return!1}}),Modernizr.addTest("websocketsbinary",function(){var e,t="https:"==location.protocol?"wss":"ws";if("WebSocket"in window){if(e="binaryType"in WebSocket.prototype)return e;try{return!!new WebSocket(t+"://.").binaryType}catch(r){}}return!1}),Modernizr.addTest("atobbtoa","atob"in window&&"btoa"in window,{aliases:["atob-btoa"]}),Modernizr.addTest("framed",window.location!=top.location),Modernizr.addTest("sharedworkers","SharedWorker"in window),Modernizr.addTest("webworkers","Worker"in window);var classes=[];Modernizr.addTest("contains",is(String.prototype.contains,"function"));var hasOwnProp;!function(){var e={}.hasOwnProperty;hasOwnProp=is(e,"undefined")||is(e.call,"undefined")?function(e,t){return t in e&&is(e.constructor.prototype[t],"undefined")}:function(t,r){return e.call(t,r)}}();var docElement=document.documentElement;Modernizr.addTest("contextmenu","contextMenu"in docElement&&"HTMLMenuItemElement"in window),Modernizr.addTest("cssall","all"in docElement.style),Modernizr.addTest("willchange","willChange"in docElement.style),Modernizr.addTest("classlist","classList"in docElement),Modernizr.addTest("documentfragment",function(){return"createDocumentFragment"in document&&"appendChild"in docElement});var prefixes=ModernizrProto._config.usePrefixes?" -webkit- -moz- -o- -ms- ".split(" "):["",""];ModernizrProto._prefixes=prefixes;var isSVG="svg"===docElement.nodeName.toLowerCase();ModernizrProto._l={},ModernizrProto.on=function(e,t){this._l[e]||(this._l[e]=[]),this._l[e].push(t),Modernizr.hasOwnProperty(e)&&setTimeout(function(){Modernizr._trigger(e,Modernizr[e])},0)},ModernizrProto._trigger=function(e,t){if(this._l[e]){var r=this._l[e];setTimeout(function(){var e,n;for(e=0;ei;i++)e=0===i?"to ":"",n+=t+prefixes[i]+"linear-gradient("+e+"left top, #9f9, white);";Modernizr._config.usePrefixes&&(n+=t+"-webkit-"+r);var d=createElement("a"),a=d.style;return a.cssText=n,(""+a.backgroundImage).indexOf("gradient")>-1}),Modernizr.addTest("multiplebgs",function(){var e=createElement("a").style;return e.cssText="background:url(https://),url(https://),red url(https://)",/(url\s*\(.*?){3}/.test(e.background)}),Modernizr.addTest("opacity",function(){var e=createElement("a").style;return e.cssText=prefixes.join("opacity:.55;"),/^0.55$/.test(e.opacity)}),Modernizr.addTest("csspointerevents",function(){var e=createElement("a").style;return e.cssText="pointer-events:auto","auto"===e.pointerEvents}),Modernizr.addTest("csspositionsticky",function(){var e="position:",t="sticky",r=createElement("a"),n=r.style;return n.cssText=e+prefixes.join(t+";"+e).slice(0,-e.length),-1!==n.position.indexOf(t)}),Modernizr.addTest("regions",function(){if(isSVG)return!1;var e=Modernizr.prefixed("flowFrom"),t=Modernizr.prefixed("flowInto"),r=!1;if(!e||!t)return r;var n=createElement("iframe"),i=createElement("div"),o=createElement("div"),d=createElement("div"),a="modernizr_flow_for_regions_check";o.innerText="M",i.style.cssText="top: 150px; left: 150px; padding: 0px;",d.style.cssText="width: 50px; height: 50px; padding: 42px;",d.style[e]=a,i.appendChild(o),i.appendChild(d),docElement.appendChild(i);var s,l,c=o.getBoundingClientRect();return o.style[t]=a,s=o.getBoundingClientRect(),l=parseInt(s.left-c.left,10),docElement.removeChild(i),42==l?r=!0:(docElement.appendChild(n),c=n.getBoundingClientRect(),n.style[t]=a,s=n.getBoundingClientRect(),c.height>0&&c.height!==s.height&&0===s.height&&(r=!0)),o=d=i=n=undefined,r}),Modernizr.addTest("cssremunit",function(){var e=createElement("a").style;try{e.fontSize="3rem"}catch(t){}return/rem/.test(e.fontSize)}),Modernizr.addTest("rgba",function(){var e=createElement("a").style;return e.cssText="background-color:rgba(150,255,150,.5)",(""+e.backgroundColor).indexOf("rgba")>-1}),Modernizr.addTest("preserve3d",function(){var e=createElement("a"),t=createElement("a");e.style.cssText="display: block; transform-style: preserve-3d; transform-origin: right; transform: rotateY(40deg);",t.style.cssText="display: block; width: 9px; height: 1px; background: #000; transform-origin: right; transform: rotateY(40deg);",e.appendChild(t),docElement.appendChild(e);var r=t.getBoundingClientRect();return docElement.removeChild(e),r.width&&r.width<4}),Modernizr.addTest("createelementattrs",function(){try{return"test"==createElement('').getAttribute("name")}catch(e){return!1}},{aliases:["createelement-attrs"]}),Modernizr.addTest("dataset",function(){var e=createElement("div");return e.setAttribute("data-a-b","c"),!(!e.dataset||"c"!==e.dataset.aB)}),Modernizr.addTest("hidden","hidden"in createElement("a")),Modernizr.addTest("bdi",function(){var e=createElement("div"),t=createElement("bdi");t.innerHTML="إ",e.appendChild(t),docElement.appendChild(e);var r="rtl"===(window.getComputedStyle?getComputedStyle(t,null):t.currentStyle).direction;return docElement.removeChild(e),r}),Modernizr.addTest("outputelem","value"in createElement("output")),Modernizr.addTest("progressbar",createElement("progress").max!==undefined),Modernizr.addTest("meter",createElement("meter").max!==undefined),Modernizr.addTest("ruby",function(){function e(e,t){var r;return window.getComputedStyle?r=document.defaultView.getComputedStyle(e,null).getPropertyValue(t):e.currentStyle&&(r=e.currentStyle[t]),r}function t(){docElement.removeChild(r),r=null,n=null,i=null}var r=createElement("ruby"),n=createElement("rt"),i=createElement("rp"),o="display",d="fontSize";return r.appendChild(i),r.appendChild(n),docElement.appendChild(r),"none"==e(i,o)||"ruby"==e(r,o)&&"ruby-text"==e(n,o)||"6pt"==e(i,d)&&"6pt"==e(n,d)?(t(),!0):(t(),!1)}),Modernizr.addTest("template","content"in createElement("template")),Modernizr.addTest("time","valueAsDate"in createElement("time")),Modernizr.addTest("texttrackapi","function"==typeof createElement("video").addTextTrack),Modernizr.addTest("track","kind"in createElement("track")),Modernizr.addTest("unknownelements",function(){var e=createElement("a");return e.innerHTML="",1===e.childNodes.length}),Modernizr.addTest("capture","capture"in createElement("input")),Modernizr.addTest("fileinput",function(){if(navigator.userAgent.match(/(Android (1.0|1.1|1.5|1.6|2.0|2.1))|(Windows Phone (OS 7|8.0))|(XBLWP)|(ZuneWP)|(w(eb)?OSBrowser)|(webOS)|(Kindle\/(1.0|2.0|2.5|3.0))/))return!1;var e=createElement("input");return e.type="file",!e.disabled}),Modernizr.addTest("formattribute",function(){var e,t=createElement("form"),r=createElement("input"),n=createElement("div"),i="formtest"+(new Date).getTime(),o=!1;t.id=i;try{r.setAttribute("form",i)}catch(d){document.createAttribute&&(e=document.createAttribute("form"),e.nodeValue=i,r.setAttributeNode(e))}return n.appendChild(t),n.appendChild(r),docElement.appendChild(n),o=t.elements&&1===t.elements.length&&r.form==t,n.parentNode.removeChild(n),o}),Modernizr.addTest("placeholder","placeholder"in createElement("input")&&"placeholder"in createElement("textarea")),Modernizr.addTest("sandbox","sandbox"in createElement("iframe")),Modernizr.addTest("seamless","seamless"in createElement("iframe")),Modernizr.addTest("srcdoc","srcdoc"in createElement("iframe")),Modernizr.addAsyncTest(function(){if(!Modernizr.canvas)return!1;var e=new Image,t=createElement("canvas"),r=t.getContext("2d");e.onload=function(){addTest("apng",function(){return"undefined"==typeof t.getContext?!1:(r.drawImage(e,0,0),0===r.getImageData(0,0,1,1).data[3])})},e.src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAACGFjVEwAAAABAAAAAcMq2TYAAAANSURBVAiZY2BgYPgPAAEEAQB9ssjfAAAAGmZjVEwAAAAAAAAAAQAAAAEAAAAAAAAAAAD6A+gBAbNU+2sAAAARZmRBVAAAAAEImWNgYGBgAAAABQAB6MzFdgAAAABJRU5ErkJggg=="}),Modernizr.addTest("imgcrossorigin","crossOrigin"in createElement("img")),Modernizr.addAsyncTest(function(){var e,t,r,n=createElement("img"),i="sizes"in n;!i&&"srcset"in n?(t="data:image/gif;base64,R0lGODlhAgABAPAAAP///wAAACH5BAAAAAAALAAAAAACAAEAAAICBAoAOw==",e="data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==",r=function(){addTest("sizes",2==n.width)},n.onload=r,n.onerror=r,n.setAttribute("sizes","9px"),n.srcset=e+" 1w,"+t+" 8w",n.src=e):addTest("sizes",i)}),Modernizr.addTest("srcset","srcset"in createElement("img")),Modernizr.addTest("inputformaction",!!("formAction"in createElement("input")),{aliases:["input-formaction"]}),Modernizr.addTest("inputformenctype",!!("formEnctype"in createElement("input")),{aliases:["input-formenctype"]}),Modernizr.addTest("inputformmethod",!!("formMethod"in createElement("input"))),Modernizr.addTest("inputformtarget",!!("formtarget"in createElement("input")),{aliases:["input-formtarget"]}),Modernizr.addTest("scriptasync","async"in createElement("script")),Modernizr.addTest("scriptdefer","defer"in createElement("script")),Modernizr.addTest("stylescoped","scoped"in createElement("style")),Modernizr.addTest("inlinesvg",function(){var e=createElement("div");return e.innerHTML="","http://www.w3.org/2000/svg"==("undefined"!=typeof SVGRect&&e.firstChild&&e.firstChild.namespaceURI)}),Modernizr.addTest("textareamaxlength",!!("maxLength"in createElement("textarea"))),Modernizr.addTest("videoloop","loop"in createElement("video")),Modernizr.addTest("videopreload","preload"in createElement("video")),Modernizr.addAsyncTest(function(){if(Modernizr.webglextensions=new Boolean(!1),Modernizr.webgl){var e,t,r;try{e=createElement("canvas"),t=e.getContext("webgl")||e.getContext("experimental-webgl"),r=t.getSupportedExtensions()}catch(n){return}t!==undefined&&(Modernizr.webglextensions=new Boolean(!0));for(var i=-1,o=r.length;++i7}),Modernizr.addTest("inputsearchevent",hasEvent("search"));var inputElem=createElement("input"),inputattrs="autocomplete autofocus list placeholder max min multiple pattern required step".split(" "),attrs={};Modernizr.input=function(e){for(var t=0,r=e.length;r>t;t++)attrs[e[t]]=!!(e[t]in inputElem);return attrs.list&&(attrs.list=!(!createElement("datalist")||!window.HTMLDataListElement)),attrs}(inputattrs),Modernizr.addTest("datalistelem",Modernizr.input.list);var inputtypes="search tel url email datetime date month week time datetime-local number range color".split(" "),inputs={};Modernizr.inputtypes=function(e){for(var t,r,n,i=e.length,o="1)",d=0;i>d;d++)inputElem.setAttribute("type",t=e[d]),n="text"!==inputElem.type&&"style"in inputElem,n&&(inputElem.value=o,inputElem.style.cssText="position:absolute;visibility:hidden;",/^range$/.test(t)&&inputElem.style.WebkitAppearance!==undefined?(docElement.appendChild(inputElem),r=document.defaultView,n=r.getComputedStyle&&"textfield"!==r.getComputedStyle(inputElem,null).WebkitAppearance&&0!==inputElem.offsetHeight,docElement.removeChild(inputElem)):/^(search|tel)$/.test(t)||(n=/^(url|email)$/.test(t)?inputElem.checkValidity&&inputElem.checkValidity()===!1:inputElem.value!=o)),inputs[e[d]]=!!n;return inputs}(inputtypes);var modElem={elem:createElement("modernizr")};Modernizr._q.push(function(){delete modElem.elem}),Modernizr.addTest("csschunit",function(){var e,t=modElem.elem.style;try{t.fontSize="3ch",e=-1!==t.fontSize.indexOf("ch")}catch(r){e=!1}return e}),Modernizr.addTest("cssexunit",function(){var e,t=modElem.elem.style;try{t.fontSize="3ex",e=-1!==t.fontSize.indexOf("ex")}catch(r){e=!1}return e}),Modernizr.addTest("hsla",function(){var e=createElement("a").style;return e.cssText="background-color:hsla(120,40%,100%,.5)",contains(e.backgroundColor,"rgba")||contains(e.backgroundColor,"hsla")});var testXhrType=function(e){if("undefined"==typeof XMLHttpRequest)return!1;var t=new XMLHttpRequest;t.open("get","/",!0);try{t.responseType=e}catch(r){return!1}return"response"in t&&t.responseType==e};Modernizr.addTest("xhrresponsetypearraybuffer",testXhrType("arraybuffer")),Modernizr.addTest("xhrresponsetypeblob",testXhrType("blob")),Modernizr.addTest("xhrresponsetypedocument",testXhrType("document")),Modernizr.addTest("xhrresponsetypejson",testXhrType("json")),Modernizr.addTest("xhrresponsetypetext",testXhrType("text"));var toStringFn={}.toString;Modernizr.addTest("svgclippaths",function(){return!!document.createElementNS&&/SVGClipPath/.test(toStringFn.call(document.createElementNS("http://www.w3.org/2000/svg","clipPath")))}),Modernizr.addTest("svgforeignobject",function(){return!!document.createElementNS&&/SVGForeignObject/.test(toStringFn.call(document.createElementNS("http://www.w3.org/2000/svg","foreignObject")))}),Modernizr.addTest("smil",function(){return!!document.createElementNS&&/SVGAnimate/.test(toStringFn.call(document.createElementNS("http://www.w3.org/2000/svg","animate")))});var testStyles=ModernizrProto.testStyles=injectElementWithStyles;Modernizr.addTest("hiddenscroll",function(){return testStyles("#modernizr {width:100px;height:100px;overflow:scroll}",function(e){return e.offsetWidth===e.clientWidth})}),Modernizr.addTest("mathml",function(){var e;return testStyles("#modernizr{position:absolute;display:inline-block}",function(t){t.innerHTML+="xxyy",e=t.offsetHeight>t.offsetWidth}),e}),Modernizr.addTest("touchevents",function(){var e;if("ontouchstart"in window||window.DocumentTouch&&document instanceof DocumentTouch)e=!0;else{var t=["@media (",prefixes.join("touch-enabled),("),"heartz",")","{#modernizr{top:9px;position:absolute}}"].join("");testStyles(t,function(t){e=9===t.offsetTop})}return e}),Modernizr.addTest("unicoderange",function(){return Modernizr.testStyles('@font-face{font-family:"unicodeRange";src:local("Arial");unicode-range:U+0020,U+002E}#modernizr span{font-size:20px;display:inline-block;font-family:"unicodeRange",monospace}#modernizr .mono{font-family:monospace}',function(e){for(var t=[".",".","m","m"],r=0;r=9,i=533>t&&e.match(/android/gi);return r||i||n}();blacklist?Modernizr.addTest("fontface",!1):testStyles('@font-face {font-family:"font";src:url("https://")}',function(e,t){var r=document.getElementById("smodernizr"),n=r.sheet||r.styleSheet,i=n?n.cssRules&&n.cssRules[0]?n.cssRules[0].cssText:n.cssText||"":"",o=/src/i.test(i)&&0===i.indexOf(t.split(" ")[0]);Modernizr.addTest("fontface",o)}),testStyles('#modernizr{font:0/0 a}#modernizr:after{content:":)";visibility:hidden;font:7px/1 a}',function(e){Modernizr.addTest("generatedcontent",e.offsetHeight>=7)}),Modernizr.addTest("hairline",function(){return testStyles("#modernizr {border:.5px solid transparent}",function(e){return 1===e.offsetHeight})}),Modernizr.addTest("cssinvalid",function(){return testStyles("#modernizr input{height:0;border:0;padding:0;margin:0;width:10px} #modernizr input:invalid{width:50px}",function(e){var t=createElement("input");return t.required=!0,e.appendChild(t),t.clientWidth>10})}),testStyles("#modernizr div {width:100px} #modernizr :last-child{width:200px;display:block}",function(e){Modernizr.addTest("lastchild",e.lastChild.offsetWidth>e.firstChild.offsetWidth)},2),testStyles("#modernizr div {width:1px} #modernizr div:nth-child(2n) {width:2px;}",function(e){for(var t=e.getElementsByTagName("div"),r=!0,n=0;5>n;n++)r=r&&t[n].offsetWidth===n%2+1;Modernizr.addTest("nthchild",r)},5),testStyles("#modernizr{overflow: scroll; width: 40px; height: 40px; }#"+prefixes.join("scrollbar{width:0px} #modernizr::").split("#").slice(1).join("#")+"scrollbar{width:0px}",function(e){Modernizr.addTest("cssscrollbar",40==e.scrollWidth)}),Modernizr.addTest("siblinggeneral",function(){return testStyles("#modernizr div {width:100px} #modernizr div ~ div {width:200px;display:block}",function(e){return 200==e.lastChild.offsetWidth},2)}),testStyles("#modernizr{position: absolute; top: -10em; visibility:hidden; font: normal 10px arial;}#subpixel{float: left; font-size: 33.3333%;}",function(e){var t=e.firstChild;t.innerHTML="This is a text written in Arial",Modernizr.addTest("subpixelfont",window.getComputedStyle?"44px"!==window.getComputedStyle(t,null).getPropertyValue("width"):!1)},1,["subpixel"]),Modernizr.addTest("cssvalid",function(){return testStyles("#modernizr input{height:0;border:0;padding:0;margin:0;width:10px} #modernizr input:valid{width:50px}",function(e){var t=createElement("input");return e.appendChild(t),t.clientWidth>10})}),testStyles("#modernizr { height: 50vh; }",function(e){var t=parseInt(window.innerHeight/2,10),r=parseInt((window.getComputedStyle?getComputedStyle(e,null):e.currentStyle).height,10);Modernizr.addTest("cssvhunit",r==t)}),testStyles("#modernizr1{width: 50vmax}#modernizr2{width:50px;height:50px;overflow:scroll}#modernizr3{position:fixed;top:0;left:0;bottom:0;right:0}",function(e){var t=e.childNodes[2],r=e.childNodes[1],n=e.childNodes[0],i=parseInt((r.offsetWidth-r.clientWidth)/2,10),o=n.clientWidth/100,d=n.clientHeight/100,a=parseInt(50*Math.max(o,d),10),s=parseInt((window.getComputedStyle?getComputedStyle(t,null):t.currentStyle).width,10);Modernizr.addTest("cssvmaxunit",roundedEquals(a,s)||roundedEquals(a,s-i))},3),testStyles("#modernizr1{width: 50vm;width:50vmin}#modernizr2{width:50px;height:50px;overflow:scroll}#modernizr3{position:fixed;top:0;left:0;bottom:0;right:0}",function(e){var t=e.childNodes[2],r=e.childNodes[1],n=e.childNodes[0],i=parseInt((r.offsetWidth-r.clientWidth)/2,10),o=n.clientWidth/100,d=n.clientHeight/100,a=parseInt(50*Math.min(o,d),10),s=parseInt((window.getComputedStyle?getComputedStyle(t,null):t.currentStyle).width,10);Modernizr.addTest("cssvminunit",roundedEquals(a,s)||roundedEquals(a,s-i))},3),testStyles("#modernizr { width: 50vw; }",function(e){var t=parseInt(window.innerWidth/2,10),r=parseInt((window.getComputedStyle?getComputedStyle(e,null):e.currentStyle).width,10);Modernizr.addTest("cssvwunit",r==t)}),Modernizr.addTest("details",function(){var e,t=createElement("details");return"open"in t?(testStyles("#modernizr details{display:block}",function(r){r.appendChild(t),t.innerHTML="ab",e=t.offsetHeight,t.open=!0,e=e!=t.offsetHeight}),e):!1}),Modernizr.addTest("oninput",function(){var e,t=createElement("input");if(t.setAttribute("oninput","return"),hasEvent("oninput",docElement)||"function"==typeof t.oninput)return!0;try{var r=document.createEvent("KeyboardEvent");e=!1;var n=function(t){e=!0,t.preventDefault(),t.stopPropagation()};r.initKeyEvent("keypress",!0,!0,window,!1,!1,!1,!1,0,"e".charCodeAt(0)),docElement.appendChild(t),t.addEventListener("input",n,!1),t.focus(),t.dispatchEvent(r),t.removeEventListener("input",n,!1),docElement.removeChild(t)}catch(i){e=!1}return e}),Modernizr.addTest("localizednumber",function(){if(!Modernizr.inputtypes.number)return!1;if(!Modernizr.formvalidation)return!1;var e,t=createElement("div"),r=getBody(),n=function(){return docElement.insertBefore(r,docElement.firstElementChild||docElement.firstChild)}();t.innerHTML='';var i=t.childNodes[0];n.appendChild(t),i.focus();try{document.execCommand("InsertText",!1,"1,1")}catch(o){}return e="number"===i.type&&1.1===i.valueAsNumber&&i.checkValidity(),n.removeChild(t),r.fake&&n.parentNode.removeChild(n),e});var mq=function(){var e=window.matchMedia||window.msMatchMedia;return e?function(t){var r=e(t);return r&&r.matches||!1}:function(e){var t=!1;return injectElementWithStyles("@media "+e+" { #modernizr { position: absolute; } }",function(e){t="absolute"==(window.getComputedStyle?window.getComputedStyle(e,null):e.currentStyle).position}),t}}();ModernizrProto.mq=mq,Modernizr.addTest("mediaqueries",mq("only all"));var omPrefixes="Moz O ms Webkit",domPrefixes=ModernizrProto._config.usePrefixes?omPrefixes.toLowerCase().split(" "):[];ModernizrProto._domPrefixes=domPrefixes,Modernizr.addTest("pointerevents",function(){var e=!1,t=domPrefixes.length;for(e=Modernizr.hasEvent("pointerdown");t--&&!e;)hasEvent(domPrefixes[t]+"pointerdown")&&(e=!0);return e}),Modernizr.addTest("fileinputdirectory",function(){var e=createElement("input"),t="directory";if(e.type="file",t in e)return!0;for(var r=0,n=domPrefixes.length;n>r;r++)if(domPrefixes[r]+t in e)return!0;return!1});var cssomPrefixes=ModernizrProto._config.usePrefixes?omPrefixes.split(" "):[];ModernizrProto._cssomPrefixes=cssomPrefixes;var atRule=function(e){var t,r=prefixes.length,n=window.CSSRule;if("undefined"==typeof n)return undefined;if(!e)return!1;if(e=e.replace(/^@/,""),t=e.replace(/-/g,"_").toUpperCase()+"_RULE",t in n)return"@"+e;for(var i=0;r>i;i++){var o=prefixes[i],d=o.toUpperCase()+"_"+t;if(d in n)return"@-"+o.toLowerCase()+"-"+e}return!1};ModernizrProto.atRule=atRule;var mStyle={style:modElem.elem.style};Modernizr._q.unshift(function(){delete mStyle.style});var testProp=ModernizrProto.testProp=function(e,t,r){return testProps([e],undefined,t,r)};Modernizr.addTest("textshadow",testProp("textShadow","1px 1px")),ModernizrProto.testAllProps=testPropsAll;var prefixed=ModernizrProto.prefixed=function(e,t,r){return 0===e.indexOf("@")?atRule(e):(-1!=e.indexOf("-")&&(e=cssToDOM(e)),t?testPropsAll(e,t,r):testPropsAll(e,"pfx"))};Modernizr.addTest("batteryapi",!!prefixed("battery",navigator),{aliases:["battery-api"]});var crypto=prefixed("crypto",window);Modernizr.addTest("crypto",!!prefixed("subtle",crypto)),Modernizr.addTest("dart",!!prefixed("startDart",navigator)),Modernizr.addTest("forcetouch",function(){return hasEvent(prefixed("mouseforcewillbegin",window,!1),window)?MouseEvent.WEBKIT_FORCE_AT_MOUSE_DOWN&&MouseEvent.WEBKIT_FORCE_AT_FORCE_MOUSE_DOWN:!1}),Modernizr.addTest("fullscreen",!(!prefixed("exitFullscreen",document,!1)&&!prefixed("cancelFullScreen",document,!1))),Modernizr.addTest("gamepads",!!prefixed("getGamepads",navigator));var indexeddb;try{indexeddb=prefixed("indexedDB",window)}catch(e){}Modernizr.addTest("indexeddb",!!indexeddb),indexeddb&&Modernizr.addTest("indexeddb.deletedatabase","deleteDatabase"in indexeddb),Modernizr.addAsyncTest(function(){var e,t,r,n="detect-blob-support",i=!1;try{e=prefixed("indexedDB",window)}catch(o){}if(!Modernizr.indexeddb||!Modernizr.indexeddb.deletedatabase)return!1;try{e.deleteDatabase(n).onsuccess=function(){t=e.open(n,1),t.onupgradeneeded=function(){t.result.createObjectStore("store")},t.onsuccess=function(){r=t.result;try{r.transaction("store","readwrite").objectStore("store").put(new Blob,"key"),i=!0}catch(o){i=!1}finally{addTest("indexeddbblob",i),r.close(),e.deleteDatabase(n)}}}}catch(o){addTest("indexeddbblob",!1)}}),Modernizr.addTest("intl",!!prefixed("Intl",window)),Modernizr.addTest("pagevisibility",!!prefixed("hidden",document,!1)),Modernizr.addTest("performance",!!prefixed("performance",window)),Modernizr.addTest("pointerlock",!!prefixed("exitPointerLock",document)),Modernizr.addTest("quotamanagement",function(){var e=prefixed("temporaryStorage",navigator),t=prefixed("persistentStorage",navigator);return!(!e||!t)}),Modernizr.addTest("requestanimationframe",!!prefixed("requestAnimationFrame",window),{aliases:["raf"]}),Modernizr.addTest("vibrate",!!prefixed("vibrate",navigator)),Modernizr.addTest("webintents",!!prefixed("startActivity",navigator)),Modernizr.addTest("lowbattery",function(){var e=.2,t=prefixed("battery",navigator);return!!(t&&!t.charging&&t.level<=e)});var crypto=prefixed("crypto",window),supportsGetRandomValues;if(crypto&&"getRandomValues"in crypto&&"Uint32Array"in window){var array=new Uint32Array(10),values=crypto.getRandomValues(array);supportsGetRandomValues=values&&is(values[0],"number")}Modernizr.addTest("getrandomvalues",!!supportsGetRandomValues),Modernizr.addTest("backgroundblendmode",prefixed("backgroundBlendMode","text")),Modernizr.addTest("objectfit",!!prefixed("objectFit"),{aliases:["object-fit"]}),Modernizr.addTest("wrapflow",function(){var e=prefixed("wrapFlow");if(!e||isSVG)return!1;var t=e.replace(/([A-Z])/g,function(e,t){return"-"+t.toLowerCase()}).replace(/^ms-/,"-ms-"),r=createElement("div"),n=createElement("div"),i=createElement("span");n.style.cssText="position: absolute; left: 50px; width: 100px; height: 20px;"+t+":end;",i.innerText="X",r.appendChild(n),r.appendChild(i),docElement.appendChild(r);var o=i.offsetLeft;return docElement.removeChild(r),n=i=r=undefined,150==o}),Modernizr.addTest("filesystem",!!prefixed("requestFileSystem",window)),Modernizr.addTest("requestautocomplete",!!prefixed("requestAutocomplete",createElement("form"))),Modernizr.addTest("speechrecognition",!!prefixed("SpeechRecognition",window));var url=prefixed("URL",window,!1);url=url&&window[url],Modernizr.addTest("bloburls",url&&"revokeObjectURL"in url&&"createObjectURL"in url),Modernizr.addAsyncTest(function(){function e(){addTest("transferables",!1),t()}function t(){a&&URL.revokeObjectURL(a),s&&s.terminate(),i&&clearTimeout(i)}var r=!!(Modernizr.blobconstructor&&Modernizr.bloburls&&Modernizr.webworkers&&Modernizr.typedarrays);if(!r)return addTest("transferables",!1);try{var n,i,o='var hello = "world"',d=new Blob([o],{type:"text/javascript"}),a=URL.createObjectURL(d),s=new Worker(a);s.onerror=e,i=setTimeout(e,200),n=new ArrayBuffer(1),s.postMessage(n,[n]),addTest("transferables",0===n.byteLength),t()}catch(l){e()}}),Modernizr.addTest("getusermedia",!!prefixed("getUserMedia",navigator)),Modernizr.addTest("peerconnection",!!prefixed("RTCPeerConnection",window)),Modernizr.addTest("datachannel",function(){if(!Modernizr.peerconnection)return!1;for(var e=0,t=domPrefixes.length;t>e;e++){var r=window[domPrefixes[e]+"RTCPeerConnection"];if(r){var n=new r({iceServers:[{url:"stun:0"}]});return"createDataChannel"in n}}return!1}),Modernizr.addTest("matchmedia",!!prefixed("matchMedia",window)),ModernizrProto.testAllProps=testAllProps,Modernizr.addTest("ligatures",testAllProps("fontFeatureSettings",'"liga" 1')),Modernizr.addTest("cssanimations",testAllProps("animationName","a",!0)),Modernizr.addTest("csspseudoanimations",function(){var e=!1;if(!Modernizr.cssanimations||!window.getComputedStyle)return e;var t=["@",Modernizr._prefixes.join("keyframes csspseudoanimations { from { font-size: 10px; } }@").replace(/\@$/,""),'#modernizr:before { content:" "; font-size:5px;',Modernizr._prefixes.join("animation:csspseudoanimations 1ms infinite;"),"}"].join("");return Modernizr.testStyles(t,function(t){e="10px"===window.getComputedStyle(t,":before").getPropertyValue("font-size")}),e}),Modernizr.addTest("appearance",testAllProps("appearance")),Modernizr.addTest("backdropfilter",testAllProps("backdropFilter")),Modernizr.addTest("backgroundcliptext",function(){return testAllProps("backgroundClip","text")}),Modernizr.addTest("bgpositionxy",function(){return testAllProps("backgroundPositionX","3px",!0)&&testAllProps("backgroundPositionY","5px",!0)}),Modernizr.addTest("bgrepeatround",testAllProps("backgroundRepeat","round")),Modernizr.addTest("bgrepeatspace",testAllProps("backgroundRepeat","space")),Modernizr.addTest("backgroundsize",testAllProps("backgroundSize","100%",!0)),Modernizr.addTest("bgsizecover",testAllProps("backgroundSize","cover")),Modernizr.addTest("borderimage",testAllProps("borderImage","url() 1",!0)),Modernizr.addTest("borderradius",testAllProps("borderRadius","0px",!0)),Modernizr.addTest("boxshadow",testAllProps("boxShadow","1px 1px",!0)),Modernizr.addTest("boxsizing",testAllProps("boxSizing","border-box",!0)&&(document.documentMode===undefined||document.documentMode>7)),function(){Modernizr.addTest("csscolumns",function(){var e=!1,t=testAllProps("columnCount");try{(e=!!t)&&(e=new Boolean(e))}catch(r){}return e});for(var e,t,r=["Width","Span","Fill","Gap","Rule","RuleColor","RuleStyle","RuleWidth","BreakBefore","BreakAfter","BreakInside"],n=0;n9)}),Modernizr.addTest("flexbox",testAllProps("flexBasis","1px",!0)),Modernizr.addTest("flexboxlegacy",testAllProps("boxDirection","reverse",!0)),Modernizr.addTest("flexboxtweener",testAllProps("flexAlign","end",!0)),Modernizr.addTest("flexwrap",testAllProps("flexWrap","wrap",!0)),Modernizr.addTest("cssmask",testAllProps("maskRepeat","repeat-x",!0)),Modernizr.addTest("overflowscrolling",testAllProps("overflowScrolling","touch",!0)),Modernizr.addTest("cssreflections",testAllProps("boxReflect","above",!0)),Modernizr.addTest("cssresize",testAllProps("resize","both",!0)),Modernizr.addTest("scrollsnappoints",testAllProps("scrollSnapType")),Modernizr.addTest("shapes",testAllProps("shapeOutside","content-box",!0)),Modernizr.addTest("textalignlast",testAllProps("textAlignLast")),Modernizr.addTest("csstransforms",function(){return-1===navigator.userAgent.indexOf("Android 2.")&&testAllProps("transform","scale(1)",!0)}),Modernizr.addTest("csstransforms3d",function(){var e=!!testAllProps("perspective","1px",!0),t=Modernizr._config.usePrefixes;if(e&&(!t||"webkitPerspective"in docElement.style)){var r,n="#modernizr{width:0;height:0}";Modernizr.supports?r="@supports (perspective: 1px)":(r="@media (transform-3d)",t&&(r+=",(-webkit-transform-3d)")),r+="{#modernizr{width:7px;height:18px;margin:0;padding:0;border:0}}",testStyles(n+r,function(t){e=7===t.offsetWidth&&18===t.offsetHeight})}return e}),Modernizr.addTest("csstransitions",testAllProps("transition","all",!0)),Modernizr.addTest("csspseudotransitions",function(){var e=!1;if(!Modernizr.csstransitions||!window.getComputedStyle)return e;var t='#modernizr:before { content:" "; font-size:5px;'+Modernizr._prefixes.join("transition:0s 100s;")+"}#modernizr.trigger:before { font-size:10px; }";return Modernizr.testStyles(t,function(t){window.getComputedStyle(t,":before").getPropertyValue("font-size"),t.className+="trigger",e="5px"===window.getComputedStyle(t,":before").getPropertyValue("font-size")}),e}),Modernizr.addTest("userselect",testAllProps("userSelect","none",!0)),testRunner(),delete ModernizrProto.addTest,delete ModernizrProto.addAsyncTest;for(var i=0;i Date: Tue, 23 May 2017 12:48:51 +0200 Subject: [PATCH 149/159] Update server side code for collecting new attributes --- .../app/controllers/ExtensionController.java | 16 +- website/app/models/ExtensionDataEntity.java | 353 ++++++++++++++++++ .../models/ExtensionDataEntityManager.java | 41 +- website/app/views/extension.scala.html | 4 + 4 files changed, 411 insertions(+), 3 deletions(-) diff --git a/website/app/controllers/ExtensionController.java b/website/app/controllers/ExtensionController.java index 5243a96..26c0328 100644 --- a/website/app/controllers/ExtensionController.java +++ b/website/app/controllers/ExtensionController.java @@ -71,7 +71,6 @@ public static Result addFingerprintFromExtension(String uuid){ create = true; } - if (update) { em.updateLastFP(uuid, currentTime); } @@ -87,6 +86,7 @@ public static Result addFingerprintFromExtension(String uuid){ } 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], @@ -97,7 +97,19 @@ public static Result addFingerprintFromExtension(String uuid){ 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); + 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(); diff --git a/website/app/models/ExtensionDataEntity.java b/website/app/models/ExtensionDataEntity.java index 7fb7636..9bb056e 100644 --- a/website/app/models/ExtensionDataEntity.java +++ b/website/app/models/ExtensionDataEntity.java @@ -454,6 +454,330 @@ 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 websocketError; + + @Basic + @javax.persistence.Column(name = "websocketError") + public String getWebsocketError() { + return websocketError; + } + + public void setWebsocketError(String websocketError) { + this.websocketError = websocketError; + } + + 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; @@ -603,6 +927,35 @@ public HashMap fpToHashMap(){ 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("websocketError",this.getWebsocketError()); + fpHashMap.put("errorsGenerated",this.getErrorsGenerated()); + fpHashMap.put("unknownImageError",this.getUnknownImageError()); + fpHashMap.put("fontsEnum",this.getFontsEnum()); + fpHashMap.put("audio",this.getAudio()); + return fpHashMap; } diff --git a/website/app/models/ExtensionDataEntityManager.java b/website/app/models/ExtensionDataEntityManager.java index 233dcee..6f7a583 100644 --- a/website/app/models/ExtensionDataEntityManager.java +++ b/website/app/models/ExtensionDataEntityManager.java @@ -25,7 +25,17 @@ public ExtensionDataEntity createFP(String id, String addressHttp, Timestamp cre 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 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); @@ -64,6 +74,35 @@ public ExtensionDataEntity createFP(String id, String addressHttp, Timestamp cre 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; }); diff --git a/website/app/views/extension.scala.html b/website/app/views/extension.scala.html index 16939bc..ea12c23 100644 --- a/website/app/views/extension.scala.html +++ b/website/app/views/extension.scala.html @@ -9,6 +9,9 @@ + + + + @@ -104,6 +105,7 @@ fontsFlash = resolutionFlash = languageFlash = platformFlash = "Flash not detected"; } + jsRoutes.controllers.FPController.addFingerprint().ajax({ data: JSON.stringify({ userAgentHttp: '@request.getHeader("User-Agent")', @@ -114,7 +116,7 @@ timezoneJs: timezone, resolutionJs: resolution, localJs: domLocalStorage, sessionJs: domSessionStorage, IEDataJs: ieUserData, canvasJs: canvasData, fontsFlash: fontsFlash, webGLJs: webGLData, vendorWebGLJs: webGLVendor,rendererWebGLJs: webGLRenderer, resolutionFlash: resolutionFlash,languageFlash: languageFlash,platformFlash: platformFlash, - adBlock: document.getElementById('ads')? 'no' : 'yes', audioData: audioData}), + adBlock: document.getElementById('ads')? 'no' : 'yes', audioData: audioData, blockExtensionsData: blockExtensionsData}), contentType:"application/json", success: function(res){ 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();