package DAOs;
import java.security.NoSuchAlgorithmException;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Timestamp;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.TimeZone;
import java.util.TreeSet;
import java.util.UUID;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpSession;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import beans.CharacteristicBean;
import beans.CharacteristicsBean;
import beans.HistoryBean;
import beans.HistoryListBean;
import beans.UniquenessBean;
import datastructures.Fingerprint;
public class FingerprintDAO {
private static final String insertSampleStr = "INSERT INTO `Samples`(`SampleUUID`, `FingerprintHash`, `IP`, `TimeStamp`, `AllHeaders`, `ContrastLevel`, `UserAgent`, `AcceptHeaders`, `Platform`, `PlatformFlash`, `PluginDetails`, `TimeZone`, `ScreenDetails`, `ScreenDetailsFlash`, `ScreenDetailsCSS`, `LanguageFlash`, `Fonts`, `FontsJS_CSS`, `FontsCSS`, `CharSizes`, `CookiesEnabled`, `SuperCookieLocalStorage`, `SuperCookieSessionStorage`, `SuperCookieUserData`, `HstsEnabled`, `IndexedDBEnabled`, `DoNotTrack`, `ClockDifference`, `DateTime`, `MathTan`, `UsingTor`, `TbbVersion`, `AdsBlockedGoogle`, `AdsBlockedBanner`, `AdsBlockedScript`, `LikeShareFacebook`, `LikeShareTwitter`, `LikeShareReddit`, `Canvas`, `WebGLVendor`, `WebGLRenderer`, `TouchPoints`, `TouchEvent`, `TouchStart`, `AudioFingerprintPXI`, `AudioFingerprintPXIFullBuffer`, `AudioFingerprintNtVc`, `AudioFingerprintCC`, `AudioFingerprintHybrid`) VALUES(?, ?, ?, NOW(), ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);";
private static final String getSampleCountStr = "SELECT COUNT(*) FROM `Samples`;";
private static final String getSampleCountVersionAwareStr = "SELECT `BrowserprintVersion`, `Count` FROM `CountBrowserprintVersion`;";
private static final String selectSampleStr = "SELECT `AllHeaders`, `ContrastLevel`, `UserAgent`, `AcceptHeaders`, `Platform`, `PlatformFlash`, `PluginDetails`, `TimeZone`, `ScreenDetails`, `ScreenDetailsFlash`, `ScreenDetailsCSS`, `LanguageFlash`, `Fonts`, `FontsJS_CSS`, `FontsCSS`, `CharSizes`, `CookiesEnabled`, `SuperCookieLocalStorage`, `SuperCookieSessionStorage`, `SuperCookieUserData`, `HstsEnabled`, `IndexedDBEnabled`, `DoNotTrack`, `ClockDifference`, `DateTime`, `MathTan`, `UsingTor`, `TbbVersion`, `AdsBlockedGoogle`, `AdsBlockedBanner`, `AdsBlockedScript`, `LikeShareFacebook`, `LikeShareTwitter`, `LikeShareReddit`, `Canvas`, `WebGLVendor`, `WebGLRenderer`, `TouchPoints`, `TouchEvent`, `TouchStart`, `AudioFingerprintPXI`, `AudioFingerprintPXIFullBuffer`, `AudioFingerprintNtVc`, `AudioFingerprintCC`, `AudioFingerprintHybrid` FROM `Samples` WHERE `SampleUUID` = ?;";
private static final String selectSampleSetIDHistory = "SELECT `SampleUUID`, `Timestamp` FROM `SampleSets` INNER JOIN `Samples` USING (`SampleID`) WHERE `SampleSetID` = ? ORDER BY `Timestamp` DESC;";
private static final String NO_JAVASCRIPT = "No JavaScript";
private static final String NOT_SUPPORTED = "Not supported";
public static final ImmutablePair<Integer, String> processFingerprint(Fingerprint fingerprint, HttpSession session, CharacteristicsBean chrsbean, UniquenessBean uniquenessbean) {
Connection conn = null;
try {
conn = Database.getConnection();
conn.setReadOnly(false);
/*
* Check if we've seen this sample before.
*/
ImmutablePair<Integer, String> sampleIDs;
{
sampleIDs = checkSampleChanged(conn, fingerprint);
}
if (sampleIDs.left == null) {
/*
* We haven't seen this sample before.
* Record it.
*/
{
sampleIDs = insertSample(conn, fingerprint);
QuestionnaireDAO.insertQuestionnaireAnswers(conn, session, sampleIDs.left);
}
/*
* Insert SampleID into SampleSets table.
*/
insertSampleSet(conn, fingerprint, sampleIDs.left);
/*
* Save statistics of the fingerprint.
*/
StatisticsDAO.saveStatistics(sampleIDs.left, fingerprint);
}
getFingerprintBeans(conn, fingerprint, chrsbean, uniquenessbean);
return new ImmutablePair<Integer, String>(sampleIDs.left, sampleIDs.right);
} catch (Exception e) {
e.printStackTrace();
} finally {
// Close the connection
// Finally triggers even if we return
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// Ignore
}
}
}
return null;
}
/**
* Takes a sampleID, gets the fingerprint associated with it, and then fills a CharacteristicsBean and UniquenessBean with the fingerprint's data and statistics.
*
* @param sampleID
* @param chrsbean
* @param uniquenessbean
* @return The fingerprint, or null if no fingerprint with the specified sampleID was found.
* @throws SQLException
* @throws NoSuchAlgorithmException
*/
public static Fingerprint getFingerprintBeans(String sampleUUID, CharacteristicsBean chrsbean, UniquenessBean uniquenessbean) throws SQLException, NoSuchAlgorithmException {
Connection conn = Database.getConnection();
conn.setReadOnly(true);
Fingerprint fingerprint = getFingerprintFromSampleID(conn, sampleUUID);
if (fingerprint == null) {
conn.close();
return null;
}
getFingerprintBeans(conn, fingerprint, chrsbean, uniquenessbean);
conn.close();
return fingerprint;
}
/**
* Takes a fingerprint and then fills a CharacteristicsBean and UniquenessBean with the fingerprint's data and statistics.
*
* @param conn
* @param fingerprint
* @param chrsbean
* @param uniquenessbean
* @throws SQLException
* @throws NoSuchAlgorithmException
*/
private static void getFingerprintBeans(Connection conn, Fingerprint fingerprint, CharacteristicsBean chrsbean, UniquenessBean uniquenessbean) throws SQLException, NoSuchAlgorithmException {
/*
* Get number of samples.
*/
TreeSet<VersionCount> sampleCounts = getSampleCountVersionAware(conn);
int totalSamples = sampleCounts.first().getCount();
/*
* Get uniqueness.
*/
int sampleOccurrences = getSampleOccurrences(conn, fingerprint);
if (sampleOccurrences == 1) {
uniquenessbean.setUnique(true);
} else {
uniquenessbean.setUnique(false);
}
uniquenessbean.setNum_samples(totalSamples);
uniquenessbean.setInX(((double) totalSamples) / ((double) sampleOccurrences));
uniquenessbean.setBits(Math.abs(Math.log(uniquenessbean.getInX()) / Math.log(2)));
uniquenessbean.setNum_occurrences(sampleOccurrences);
/*
* Get each characteristic.
*/
ArrayList<CharacteristicBean> characteristics = chrsbean.getCharacteristics();
{
CharacteristicBean bean = getCharacteristicBean(conn, sampleCounts.lower(new VersionCount(9 + 1)).getCount(), "ContrastLevel", fingerprint.getContrastLevel());
bean.setName("Monitor Contrast Level");
bean.setNameHoverText("A rough measure of the level of contrast of the monitor the browser is being displayed on.");
String contrastLevelStr = "";
if(fingerprint.getContrastLevel() == 0){
contrastLevelStr = "Normal or low contrast.";
}
if(fingerprint.getContrastLevel() == 1){
contrastLevelStr = "High contrast.";
}
if(fingerprint.getContrastLevel() == 2){
contrastLevelStr = "Extreme contrast.";
}
bean.setValue(contrastLevelStr);
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "UserAgent", fingerprint.getUser_agent());
bean.setName("User Agent");
bean.setNameHoverText("The User-Agent header sent with the HTTP request for the page.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "AcceptHeaders", fingerprint.getAccept_headers());
bean.setName("HTTP_ACCEPT Headers");
bean.setNameHoverText("The concatenation of three headers from the HTTP request:"
+ " The Accept request header, the Accept-Encoding request header, and the Accept-Language request header.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "Platform", fingerprint.getPlatform());
bean.setName("Platform (JavaScript)");
bean.setNameHoverText("The name of the platform the browser is running on, detected using JavaScript.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "PlatformFlash", fingerprint.getPlatformFlash());
bean.setName("Platform (Flash)");
bean.setNameHoverText("The name of the platform the browser is running on, detected using Flash.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "PluginDetails", fingerprint.getPluginDetails());
bean.setName("Browser Plugin Details");
bean.setNameHoverText("A list of the browsers installed plugins as detected using JavaScript.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "TimeZone", fingerprint.getTimeZone());
bean.setName("Time Zone");
bean.setNameHoverText("The time-zone configured on the client's machine.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "ScreenDetails", fingerprint.getScreenDetails());
bean.setColour(CharacteristicBean.DEPRECATED_COLOUR);
bean.setName("Screen Size and Colour Depth [DEPRECATED]");
bean.setNameHoverText("The screen size and colour depth of the monitor displaying the client's web browser."
+ " Deprecated because in the current implementation zooming changes the result in newer browsers.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "ScreenDetailsFlash", fingerprint.getScreenDetailsFlash());
bean.setName("Screen Size (Flash)");
bean.setNameHoverText("The resolution of the client's monitor(s)."
+ " Different from the other screen size test in that this number <i>can</i> be the cumulative resolution of the monitors in multiple monitor set ups.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, sampleCounts.lower(new VersionCount(13 + 1)).getCount(), "ScreenDetailsCSS", fingerprint.getScreenDetailsCSS());
bean.setColour(CharacteristicBean.DEPRECATED_COLOUR);
bean.setName("Screen Size (CSS) [DEPRECATED]");
bean.setNameHoverText("The screen size of the monitor displaying the client's web browser, detected using CSS."
+ " Deprecated because in the current implementation zooming changes the result in newer browsers.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "LanguageFlash", fingerprint.getLanguageFlash());
bean.setName("Language (Flash)");
bean.setNameHoverText("The language of the client's browser, as detected using Flash.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "Fonts", fingerprint.getFonts());
if (bean.getValue().equals("")) {
bean.setValue("No fonts detected");
}
bean.setName("System Fonts (Flash)");
bean.setNameHoverText("The fonts installed on the client's machine, detected using Flash.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, sampleCounts.lower(new VersionCount(6 + 1)).getCount(), "FontsJS_CSS", fingerprint.getFontsJS_CSS());
if (bean.getValue().equals("")) {
bean.setValue("No fonts detected");
}
bean.setName("System Fonts (JS/CSS)");
bean.setNameHoverText("The fonts installed on the client's machine, detected using JavaScript. Fonts list may be incomplete.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, sampleCounts.lower(new VersionCount(19 + 1)).getCount(), "FontsCSS", fingerprint.getFontsCSS());
if (bean.getValue().equals("")) {
bean.setValue("No fonts detected");
}
bean.setName("Fonts (CSS only) [streamlined]");
bean.setNameHoverText("The fonts installed on the client's machine, detected using CSS without JavaScript."
+ " Fonts list may be incomplete, and this test checks for less fonts than other tests for efficiency."
+ " CSS font fingerprinting can be blocked by disabling CSS or by disabling JavaScript using the NoScript extension (despite the test not using JavaScript).");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, sampleCounts.lower(new VersionCount(2 + 1)).getCount(), "CharSizes", fingerprint.getCharSizes());
bean.setColour(CharacteristicBean.DEPRECATED_COLOUR);
bean.setName("Character Sizes [DEPRECATED]");
bean.setNameHoverText("The height and width of a set of Unicode characters when rendered with a set of different styles (e.g. sans-serif)."
+ " Deprecated because in the current implementation zooming changes the result.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "CookiesEnabled", fingerprint.isCookiesEnabled());
bean.setName("Are Cookies Enabled?");
bean.setNameHoverText("Whether cookies are enabled.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getSuperCookieCharacteristicBean(conn, sampleCounts.lower(new VersionCount(5 + 1)).getCount(), fingerprint);
bean.setName("Limited supercookie test");
bean.setNameHoverText("Three tests of whether DOM storage is supported (and enabled) in the client's web browser."
+ " Tests for localStorage, sessionStorage, and Internet Explorer's userData.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, sampleCounts.lower(new VersionCount(21 + 1)).getCount(), "HstsEnabled", fingerprint.getHstsEnabled());
if(bean.getValue().equals(NO_JAVASCRIPT)){
bean.setValue("Unknown");
}
bean.setName("Browser supports HSTS?");
bean.setNameHoverText("HSTS is a web security enhancement that is used to make future connections to a domain exclusively HTTPS, not HTTP."
+ " HSTS can be abused to store a super cookie on your machine that can then be used to track you, theoretically without even needing JavaScript.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, sampleCounts.lower(new VersionCount(8 + 1)).getCount(), "IndexedDBEnabled", fingerprint.getIndexedDBEnabled());
bean.setName("Does the browser support IndexedDB?");
bean.setNameHoverText("Detects whether the browser supports IndexedDB, a database embedded within the browser.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "DoNotTrack", fingerprint.getDoNotTrack());
if (bean.getValue().equals(NO_JAVASCRIPT)) {
bean.setValue("No preference");
}
bean.setName("Do Not Track header");
bean.setNameHoverText("The value of the DNT (Do Not Track) header from the HTTP request.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "ClockDifference", fingerprint.getClockDifference());
bean.setName("Client/server time difference (minutes)");
bean.setNameHoverText("The approximate amount of difference between the time on the client's computer and the clock on the server."
+ " e.g., the clock on the client's computer is 5 minutes ahead of the clock on the server.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "DateTime", fingerprint.getDateTime());
bean.setName("Date/Time format");
bean.setNameHoverText("When the JavaScript function toLocaleString() is called on a date it can reveal information about the language of the browser via the names of days and months." + " For instance the output 'Thursday January 01, 10:30:00 GMT+1030 1970' reveals that English is our configured language because 'Thursday' is English." + " Additionally different browsers tend to return differently formatted results." + " For instance Opera returns the above whereas Firefox returns '1/1/1970 9:30:00 am' for the same date (UNIX epoch)." + " Additionally timezone information may be revealed." + " For instance the above were taken on a computer configured for ACST (+9:30), which is why the times shown aren't midnight.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "MathTan", fingerprint.getMathTan());
bean.setName("Math/Tan function");
bean.setNameHoverText("The same math functions run on different platforms and browsers can produce different results."
+ " In particular we are interested in the output of Math.tan(-1e300), which has been observed to produce different values depending on operating system."
+ " For instance on a 64bit Linux machine it produces the value -1.4214488238747245 and on a Windows machine it produces the value -4.987183803371025.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "UsingTor", fingerprint.isUsingTor());
bean.setName("Using Tor?");
bean.setNameHoverText("Checks whether a client's request came from a Tor exit node, and hence whether they're using Tor."
+ " It does so by performing a TorDNSEL request for each client.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, sampleCounts.lower(new VersionCount(8 + 1)).getCount(), "TbbVersion", fingerprint.getTbbVersion());
bean.setName("TBB version");
if (bean.getValue().equals("")) {
bean.setValue("No Tor Browser Bundle version detected");
}
bean.setNameHoverText("The version of the Tor Browser Bundle (TBB) you are using (if you're using the TBB).");
characteristics.add(bean);
}
{
CharacteristicBean bean = getAdsBlockedCharacteristicBean(conn, sampleCounts.lower(new VersionCount(12 + 1)).getCount(), fingerprint);
bean.setName("Blocking ads?");
bean.setNameHoverText("Checks whether ad blocking software is installed."
+ " It does so by attempting to display 2 ads and trying to call a function from a script named like an ad serving script."
+ " The Google ad may also be affected by tracker blocking software.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getLikeShareCharacteristicBean(conn, sampleCounts.lower(new VersionCount(16 + 1)).getCount(), fingerprint);
bean.setName("Blocking like/share buttons?");
bean.setNameHoverText("Checks whether software is installed that blocks or modifies like or share buttons."
+ " It does so by attempting to display 3 share buttons and checking if they're displayed properly.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "Canvas", fingerprint.getCanvas());
bean.setName("Canvas [DEPRECATED]");
bean.setColour(CharacteristicBean.DEPRECATED_COLOUR);
if (bean.getValue().equals(NO_JAVASCRIPT) == false && bean.getValue().equals(NOT_SUPPORTED) == false) {
bean.setValue("<img width=\"400\" height=\"60\" alt=\"A HTML5 canvas test\" src=\"" + bean.getValue() + "\">");
}
bean.setNameHoverText("Rendering of a specific picture with the HTML5 Canvas element following a fixed set of instructions."
+ " The picture presents some slight noticeable variations depending on the OS and the browser used."
+ " Deprecated because under some circumstances a browser can produce different canvases just by refreshing the page.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "WebGLVendor", fingerprint.getWebGLVendor());
bean.setName("WebGL Vendor");
bean.setNameHoverText("Name of the WebGL Vendor. Some browsers give the full name of the underlying graphics card used by the device.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getCharacteristicBean(conn, totalSamples, "WebGLRenderer", fingerprint.getWebGLRenderer());
bean.setName("WebGL Renderer");
bean.setNameHoverText("Name of the WebGL Renderer. Some browsers give the full name of the underlying graphics driver.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getTouchCharacteristicBean(conn, sampleCounts.lower(new VersionCount(8 + 1)).getCount(), fingerprint);
bean.setName("Touch Support");
bean.setNameHoverText("Primitive touch screen detection.");
characteristics.add(bean);
}
{
CharacteristicBean bean = getAudioTestsCharacteristicBean(conn, sampleCounts.lower(new VersionCount(17 + 1)).getCount(), fingerprint);
bean.setName("Audio Fingerprints [EXPERIMENTAL]");
bean.setColour(CharacteristicBean.EXPERIMENTAL_COLOUR);
bean.setNameHoverText("A set of fingerprinting tests that work using the AudioContext API. Based on fingerprinting code from the wild.");
characteristics.add(bean);
}
}
/**
* Insert a sample into the Samples database.
*
* @param conn
* @param fingerprint
* @return The sample ID of the inserted sample.
* @throws SQLException
* @throws NoSuchAlgorithmException
*/
private static ImmutablePair<Integer, String> insertSample(Connection conn, Fingerprint fingerprint) throws SQLException, NoSuchAlgorithmException {
PreparedStatement insertSample = conn.prepareStatement(insertSampleStr, Statement.RETURN_GENERATED_KEYS);
int index = 2;
insertSample.setString(index, fingerprint.getFingerprintHash());
++index;
insertSample.setString(index, fingerprint.getIpAddress());
++index;
insertSample.setString(index, fingerprint.getAllHeaders());
++index;
insertSample.setInt(index, fingerprint.getContrastLevel());
++index;
insertSample.setString(index, fingerprint.getUser_agent());
++index;
insertSample.setString(index, fingerprint.getAccept_headers());
++index;
insertSample.setString(index, fingerprint.getPlatform());
++index;
insertSample.setString(index, fingerprint.getPlatformFlash());
++index;
insertSample.setString(index, fingerprint.getPluginDetails());
++index;
if (fingerprint.getTimeZone() != null) {
insertSample.setString(index, fingerprint.getTimeZone());
} else {
insertSample.setNull(index, java.sql.Types.INTEGER);
}
++index;
insertSample.setString(index, fingerprint.getScreenDetails());
++index;
insertSample.setString(index, fingerprint.getScreenDetailsFlash());
++index;
insertSample.setString(index, fingerprint.getScreenDetailsCSS());
++index;
insertSample.setString(index, fingerprint.getLanguageFlash());
++index;
insertSample.setString(index, fingerprint.getFonts());
++index;
insertSample.setString(index, fingerprint.getFontsJS_CSS());
++index;
insertSample.setString(index, fingerprint.getFontsCSS());
++index;
insertSample.setString(index, fingerprint.getCharSizes());
++index;
insertSample.setBoolean(index, fingerprint.isCookiesEnabled());
++index;
if(fingerprint.getSuperCookieLocalStorage() != null){
insertSample.setBoolean(index, fingerprint.getSuperCookieLocalStorage());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
if(fingerprint.getSuperCookieSessionStorage() != null){
insertSample.setBoolean(index, fingerprint.getSuperCookieSessionStorage());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
if(fingerprint.getSuperCookieUserData() != null){
insertSample.setBoolean(index, fingerprint.getSuperCookieUserData());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
if(fingerprint.getHstsEnabled() != null){
insertSample.setBoolean(index, fingerprint.getHstsEnabled());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
if(fingerprint.getIndexedDBEnabled() != null){
insertSample.setBoolean(index, fingerprint.getIndexedDBEnabled());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
insertSample.setString(index, fingerprint.getDoNotTrack());
++index;
if (fingerprint.getClockDifference() != null) {
insertSample.setLong(index, fingerprint.getClockDifference());
} else {
insertSample.setNull(index, java.sql.Types.BIGINT);
}
++index;
insertSample.setString(index, fingerprint.getDateTime());
++index;
insertSample.setString(index, fingerprint.getMathTan());
++index;
insertSample.setBoolean(index, fingerprint.isUsingTor());
++index;
insertSample.setString(index, fingerprint.getTbbVersion());
++index;
if (fingerprint.getAdsBlockedGoogle() != null) {
insertSample.setBoolean(index, fingerprint.getAdsBlockedGoogle());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
if (fingerprint.getAdsBlockedBanner() != null) {
insertSample.setBoolean(index, fingerprint.getAdsBlockedBanner());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
if (fingerprint.getAdsBlockedScript() != null) {
insertSample.setBoolean(index, fingerprint.getAdsBlockedScript());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
if (fingerprint.getLikeShareFacebook() != null) {
insertSample.setInt(index, fingerprint.getLikeShareFacebook());
} else {
insertSample.setNull(index, java.sql.Types.INTEGER);
}
++index;
if (fingerprint.getLikeShareTwitter() != null) {
insertSample.setInt(index, fingerprint.getLikeShareTwitter());
} else {
insertSample.setNull(index, java.sql.Types.INTEGER);
}
++index;
if (fingerprint.getLikeShareReddit() != null) {
insertSample.setInt(index, fingerprint.getLikeShareReddit());
} else {
insertSample.setNull(index, java.sql.Types.INTEGER);
}
++index;
insertSample.setString(index, fingerprint.getCanvas());
++index;
insertSample.setString(index, fingerprint.getWebGLVendor());
++index;
insertSample.setString(index, fingerprint.getWebGLRenderer());
++index;
if (fingerprint.getTouchPoints() != null) {
insertSample.setLong(index, fingerprint.getTouchPoints());
} else {
insertSample.setNull(index, java.sql.Types.BIGINT);
}
++index;
if (fingerprint.getTouchEvent() != null) {
insertSample.setBoolean(index, fingerprint.getTouchEvent());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
if (fingerprint.getTouchStart() != null) {
insertSample.setBoolean(index, fingerprint.getTouchStart());
} else {
insertSample.setNull(index, java.sql.Types.BOOLEAN);
}
++index;
insertSample.setString(index, fingerprint.getAudioFingerprintPXI());
++index;
insertSample.setString(index, fingerprint.getAudioFingerprintPXIFullBuffer());
++index;
insertSample.setString(index, fingerprint.getAudioFingerprintNtVc());
++index;
insertSample.setString(index, fingerprint.getAudioFingerprintCC());
++index;
insertSample.setString(index, fingerprint.getAudioFingerprintHybrid());
/*
* Try to insert with a different random UUID until a unique one is found.
*/
String sampleUUID = null;
boolean foundUniqueUUID = false;
while (!foundUniqueUUID) {
sampleUUID = UUID.randomUUID().toString();
insertSample.setString(1, sampleUUID);
try {
insertSample.execute();
foundUniqueUUID = true;
} catch (SQLException ex) {
final int MYSQL_DUPLICATE_PK = 1062;
if(ex.getErrorCode() == MYSQL_DUPLICATE_PK){
System.err.println("Duplicate SampleUUID: " + sampleUUID);
}
else{
throw ex;
}
}
}
ResultSet rs = insertSample.getGeneratedKeys();
Integer sampleID = null;
if (rs.next()) {
sampleID = rs.getInt(1);
}
rs.close();
insertSample.close();
return new ImmutablePair<Integer, String>(sampleID, sampleUUID);
}
/**
*
* @param conn
* @param fingerprint
* @param sampleID
* @return
* @throws SQLException
*/
private static void insertSampleSet(Connection conn, Fingerprint fingerprint, Integer sampleID) throws SQLException {
String insertQuery = "INSERT INTO `SampleSets`(`SampleSetID`,`SampleID`) VALUES(?, ?);";
PreparedStatement insertSampleSet = conn.prepareStatement(insertQuery);
insertSampleSet.setInt(2, sampleID);
String selectQuery = "SELECT 1 FROM `SampleSets` WHERE `SampleSetID` = ? LIMIT 1";
PreparedStatement selectSampleSet = conn.prepareStatement(selectQuery);
if(fingerprint.getSampleSetID() != null){
/*
* Check if the SampleSetID exists.
* If it doesn't set fingerprint.sampleSetID to null so that it creates a new sampleSetID and inserts that.
*/
selectSampleSet.setString(1, fingerprint.getSampleSetID());
ResultSet rs = selectSampleSet.executeQuery();
if(!rs.next()){
fingerprint.setSampleSetID(null);
}
else{
/*
* SampleSetID exists.
* Insert new SampleID for existing SampleSetID.
*/
insertSampleSet.setString(1, fingerprint.getSampleSetID());
insertSampleSet.execute();
insertSampleSet.close();
}
}
if(fingerprint.getSampleSetID() == null) {
/*
* Try to generate a different random SampleSetUUID until a unique one is found.
* Insert whole new SampleSetID.
*/
String sampleSetID;
while(true){
sampleSetID = UUID.randomUUID().toString();
selectSampleSet.clearParameters();
selectSampleSet.setString(1, sampleSetID);
ResultSet rs = selectSampleSet.executeQuery();
if(!rs.next()){
break;
}
}
insertSampleSet.setString(1, sampleSetID);
insertSampleSet.execute();
fingerprint.setSampleSetID(sampleSetID);
insertSampleSet.close();
}
}
/**
* Returns the sampleID of the matching sample if we've seen this sample (with SampleSetID) before.
* Otherwise returns null.
*
* @param conn
* @param fingerprint
* @return
* @throws SQLException
* @throws NoSuchAlgorithmException
*/
private static ImmutablePair<Integer, String> checkSampleChanged(Connection conn, Fingerprint fingerprint) throws SQLException {
if (fingerprint.getSampleSetID() == null) {
/*
* We know we haven't seen this sample before because there's no SampleSetID.
*/
return new ImmutablePair<Integer, String>(null, null);
}
/*
* We have seen this user before. Check if their fingerprint has changed.
*/
String query = "SELECT `Samples`.`SampleID`, `Samples`.`SampleUUID` FROM `SampleSets` INNER JOIN `Samples` ON `SampleSets`.`SampleID` = `Samples`.`SampleID` WHERE `SampleSetID` = ?"
+ " AND `ContrastLevel` = ?"
+ " AND `UserAgent`" + (fingerprint.getUser_agent() == null ? " IS NULL" : " = ?")
+ " AND `AcceptHeaders`" + (fingerprint.getAccept_headers() == null ? " IS NULL" : " = ?")
+ " AND `Platform`" + (fingerprint.getPlatform() == null ? " IS NULL" : " = ?")
+ " AND `PlatformFlash`" + (fingerprint.getPlatformFlash() == null ? " IS NULL" : " = ?")
+ " AND `PluginDetails`" + (fingerprint.getPluginDetails() == null ? " IS NULL" : " = ?")
+ " AND `TimeZone`" + (fingerprint.getTimeZone() == null ? " IS NULL" : " = ?")
+ " AND `ScreenDetails`" + (fingerprint.getScreenDetails() == null ? " IS NULL" : " = ?")
+ " AND `ScreenDetailsFlash`" + (fingerprint.getScreenDetailsFlash() == null ? " IS NULL" : " = ?")
+ " AND `ScreenDetailsCSS`" + (fingerprint.getScreenDetailsCSS() == null ? " IS NULL" : " = ?")
+ " AND `LanguageFlash`" + (fingerprint.getLanguageFlash() == null ? " IS NULL" : " = ?")
+ " AND `Fonts`" + (fingerprint.getFonts() == null ? " IS NULL" : " = ?")
+ " AND `FontsJS_CSS`" + (fingerprint.getFontsJS_CSS() == null ? " IS NULL" : " = ?")
+ " AND `FontsCSS`" + (fingerprint.getFontsCSS() == null ? " IS NULL" : " = ?")
+ " AND `CharSizes`" + (fingerprint.getCharSizes() == null ? " IS NULL" : " = ?")
+ " AND `CookiesEnabled` = ?"
+ " AND `SuperCookieLocalStorage`" + (fingerprint.getSuperCookieLocalStorage() == null ? " IS NULL" : " = ?")
+ " AND `SuperCookieSessionStorage`" + (fingerprint.getSuperCookieSessionStorage() == null ? " IS NULL" : " = ?")
+ " AND `SuperCookieUserData`" + (fingerprint.getSuperCookieUserData() == null ? " IS NULL" : " = ?")
+ " AND `HstsEnabled`" + (fingerprint.getHstsEnabled() == null ? " IS NULL" : " = ?")
+ " AND `IndexedDBEnabled`" + (fingerprint.getIndexedDBEnabled() == null ? " IS NULL" : " = ?")
+ " AND `DoNotTrack`" + (fingerprint.getDoNotTrack() == null ? " IS NULL" : " = ?")
+ " AND `ClockDifference`" + (fingerprint.getClockDifference() == null ? " IS NULL" : " = ?")
+ " AND `DateTime`" + (fingerprint.getDateTime() == null ? " IS NULL" : " = ?")
+ " AND `MathTan`" + (fingerprint.getMathTan() == null ? " IS NULL" : " = ?")
+ " AND `UsingTor` = ?"
+ " AND `TbbVersion`" + (fingerprint.getTbbVersion() == null ? " IS NULL" : " = ?")
+ " AND `AdsBlockedGoogle`" + (fingerprint.getAdsBlockedGoogle() == null ? " IS NULL" : " = ?")
+ " AND `AdsBlockedBanner`" + (fingerprint.getAdsBlockedBanner() == null ? " IS NULL" : " = ?")
+ " AND `AdsBlockedScript`" + (fingerprint.getAdsBlockedScript() == null ? " IS NULL" : " = ?")
+ " AND `LikeShareFacebook`" + (fingerprint.getLikeShareFacebook() == null ? " IS NULL" : " = ?")
+ " AND `LikeShareTwitter`" + (fingerprint.getLikeShareTwitter() == null ? " IS NULL" : " = ?")
+ " AND `LikeShareReddit`" + (fingerprint.getLikeShareReddit() == null ? " IS NULL" : " = ?")
+ " AND `Canvas`" + (fingerprint.getCanvas() == null ? " IS NULL" : " = ?")
+ " AND `WebGLVendor`" + (fingerprint.getWebGLVendor() == null ? " IS NULL" : " = ?")
+ " AND `WebGLRenderer`" + (fingerprint.getWebGLRenderer() == null ? " IS NULL" : " = ?")
+ " AND `TouchPoints`" + (fingerprint.getTouchPoints() == null ? " IS NULL" : " = ?")
+ " AND `TouchEvent`" + (fingerprint.getTouchEvent() == null ? " IS NULL" : " = ?")
+ " AND `TouchStart`" + (fingerprint.getTouchStart() == null ? " IS NULL" : " = ?")
+ " AND `AudioFingerprintPXI`" + (fingerprint.getAudioFingerprintPXI() == null ? " IS NULL" : " = ?")
+ " AND `AudioFingerprintPXIFullBuffer`" + (fingerprint.getAudioFingerprintPXIFullBuffer() == null ? " IS NULL" : " = ?")
+ " AND `AudioFingerprintNtVc`" + (fingerprint.getAudioFingerprintNtVc() == null ? " IS NULL" : " = ?")
+ " AND `AudioFingerprintCC`" + (fingerprint.getAudioFingerprintCC() == null ? " IS NULL" : " = ?")
+ " AND `AudioFingerprintHybrid`" + (fingerprint.getAudioFingerprintHybrid() == null ? " IS NULL" : " = ?") + ";";
PreparedStatement checkExists = conn.prepareStatement(query);
int index = 1;
checkExists.setString(index, fingerprint.getSampleSetID());
++index;
if (fingerprint.getContrastLevel() != null) {
checkExists.setInt(index, fingerprint.getContrastLevel());
++index;
}
if (fingerprint.getUser_agent() != null) {
checkExists.setString(index, fingerprint.getUser_agent());
++index;
}
if (fingerprint.getAccept_headers() != null) {
checkExists.setString(index, fingerprint.getAccept_headers());
++index;
}
if (fingerprint.getPlatform() != null) {
checkExists.setString(index, fingerprint.getPlatform());
++index;
}
if (fingerprint.getPlatformFlash() != null) {
checkExists.setString(index, fingerprint.getPlatformFlash());
++index;
}
if (fingerprint.getPluginDetails() != null) {
checkExists.setString(index, fingerprint.getPluginDetails());
++index;
}
if (fingerprint.getTimeZone() != null) {
checkExists.setString(index, fingerprint.getTimeZone());
++index;
}
if (fingerprint.getScreenDetails() != null) {
checkExists.setString(index, fingerprint.getScreenDetails());
++index;
}
if (fingerprint.getScreenDetailsFlash() != null) {
checkExists.setString(index, fingerprint.getScreenDetailsFlash());
++index;
}
if (fingerprint.getScreenDetailsCSS() != null) {
checkExists.setString(index, fingerprint.getScreenDetailsCSS());
++index;
}
if (fingerprint.getLanguageFlash() != null) {
checkExists.setString(index, fingerprint.getLanguageFlash());
++index;
}
if (fingerprint.getFonts() != null) {
checkExists.setString(index, fingerprint.getFonts());
++index;
}
if (fingerprint.getFontsJS_CSS() != null) {
checkExists.setString(index, fingerprint.getFontsJS_CSS());
++index;
}
if (fingerprint.getFontsCSS() != null) {
checkExists.setString(index, fingerprint.getFontsCSS());
++index;
}
if (fingerprint.getCharSizes() != null) {
checkExists.setString(index, fingerprint.getCharSizes());
++index;
}
checkExists.setBoolean(index, fingerprint.isCookiesEnabled());
++index;
if (fingerprint.getSuperCookieLocalStorage() != null) {
checkExists.setBoolean(index, fingerprint.getSuperCookieLocalStorage());
++index;
}
if (fingerprint.getSuperCookieSessionStorage() != null) {
checkExists.setBoolean(index, fingerprint.getSuperCookieSessionStorage());
++index;
}
if (fingerprint.getSuperCookieUserData() != null) {
checkExists.setBoolean(index, fingerprint.getSuperCookieUserData());
++index;
}
if (fingerprint.getHstsEnabled() != null) {
checkExists.setBoolean(index, fingerprint.getHstsEnabled());
++index;
}
if (fingerprint.getIndexedDBEnabled() != null) {
checkExists.setBoolean(index, fingerprint.getIndexedDBEnabled());
++index;
}
if (fingerprint.getDoNotTrack() != null) {
checkExists.setString(index, fingerprint.getDoNotTrack());
++index;
}
if (fingerprint.getClockDifference() != null) {
checkExists.setLong(index, fingerprint.getClockDifference());
++index;
}
if (fingerprint.getDateTime() != null) {
checkExists.setString(index, fingerprint.getDateTime());
++index;
}
if (fingerprint.getMathTan() != null) {
checkExists.setString(index, fingerprint.getMathTan());
++index;
}
checkExists.setBoolean(index, fingerprint.isUsingTor());
++index;
if (fingerprint.getTbbVersion() != null) {
checkExists.setString(index, fingerprint.getTbbVersion());
++index;
}
if (fingerprint.getAdsBlockedGoogle() != null) {
checkExists.setBoolean(index, fingerprint.getAdsBlockedGoogle());
++index;
}
if (fingerprint.getAdsBlockedBanner() != null) {
checkExists.setBoolean(index, fingerprint.getAdsBlockedBanner());
++index;
}
if (fingerprint.getAdsBlockedScript() != null) {
checkExists.setBoolean(index, fingerprint.getAdsBlockedScript());
++index;
}
if (fingerprint.getLikeShareFacebook() != null) {
checkExists.setInt(index, fingerprint.getLikeShareFacebook());
++index;
}
if (fingerprint.getLikeShareTwitter() != null) {
checkExists.setInt(index, fingerprint.getLikeShareTwitter());
++index;
}
if (fingerprint.getLikeShareReddit() != null) {
checkExists.setInt(index, fingerprint.getLikeShareReddit());
++index;
}
if (fingerprint.getCanvas() != null) {
checkExists.setString(index, fingerprint.getCanvas());
++index;
}
if (fingerprint.getWebGLVendor() != null) {
checkExists.setString(index, fingerprint.getWebGLVendor());
++index;
}
if (fingerprint.getWebGLRenderer() != null) {
checkExists.setString(index, fingerprint.getWebGLRenderer());
++index;
}
if (fingerprint.getTouchPoints() != null) {
checkExists.setInt(index, fingerprint.getTouchPoints());
++index;
}
if (fingerprint.getTouchEvent() != null) {
checkExists.setBoolean(index, fingerprint.getTouchEvent());
++index;
}
if (fingerprint.getTouchStart() != null) {
checkExists.setBoolean(index, fingerprint.getTouchStart());
++index;
}
if (fingerprint.getAudioFingerprintPXI() != null) {
checkExists.setString(index, fingerprint.getAudioFingerprintPXI());
++index;
}
if (fingerprint.getAudioFingerprintPXIFullBuffer() != null) {
checkExists.setString(index, fingerprint.getAudioFingerprintPXIFullBuffer());
++index;
}
if (fingerprint.getAudioFingerprintNtVc() != null) {
checkExists.setString(index, fingerprint.getAudioFingerprintNtVc());
++index;
}
if (fingerprint.getAudioFingerprintCC() != null) {
checkExists.setString(index, fingerprint.getAudioFingerprintCC());
++index;
}
if (fingerprint.getAudioFingerprintHybrid() != null) {
checkExists.setString(index, fingerprint.getAudioFingerprintHybrid());
++index;
}
ResultSet rs = checkExists.executeQuery();
Integer sampleID = null;
String sampleUUID = null;
if (rs.next()) {
/*
* We've seen this sample before and the fingerprint hasn't changed,
* don't log it.
*/
sampleID = rs.getInt(1);
sampleUUID = rs.getString(2);
}
rs.close();
checkExists.close();
return new ImmutablePair<Integer, String>(sampleID, sampleUUID);
}
public static int getSampleCount(Connection conn) throws SQLException {
PreparedStatement getSampleCount = conn.prepareStatement(getSampleCountStr);
ResultSet rs = getSampleCount.executeQuery();
rs.next();
int sampleCount = rs.getInt(1);
rs.close();
return sampleCount;
}
/**
* Get number of samples for each version.
* Counts include all samples with version number higher than or equal to the version number in question.
* E.g. For version 1 we have all samples, since all samples are version 1 or higher.
* For version 2 we have all samples of version 2, version 3, version 4, ..., up until the latest version.
* We only need to use this version aware version of sample count because we're adding features to the live site, so there will be fingerprints from older versions.
* @param conn
* @return
* @throws SQLException
*/
public static TreeSet<VersionCount> getSampleCountVersionAware(Connection conn) throws SQLException {
PreparedStatement getSampleCount = conn.prepareStatement(getSampleCountVersionAwareStr);
TreeSet<VersionCount> counts = new TreeSet<VersionCount>();
ResultSet rs = getSampleCount.executeQuery();
while(rs.next()){
counts.add(new VersionCount(rs.getInt(1), rs.getInt(2)));
}
Iterator<VersionCount> it = counts.descendingIterator();
int total = 0;
while(it.hasNext()){
VersionCount vc = it.next();
total += vc.getCount();
vc.setCount(total);
}
counts.add(new VersionCount(0, total));
rs.close();
return counts;
}
/**
* Check whether a fingerprint with all the given details, including matching SampleID,
* is already inside the database.
*
* @param conn
* @param fingerprint
* @return
* @throws SQLException
* @throws NoSuchAlgorithmException
*/
private static int getSampleOccurrences(Connection conn, Fingerprint fingerprint) throws SQLException, NoSuchAlgorithmException {
/*
* We have seen this user before. Check if their fingerprint has changed.
*/
String query = "SELECT `Count` FROM `CountFingerprintHash` WHERE `FingerprintHash` = ?";
PreparedStatement checkExists = conn.prepareStatement(query);
checkExists.setString(1, fingerprint.getFingerprintHash());
ResultSet rs = checkExists.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
checkExists.close();
return count;
}
/**
* Create the cookies enabled CharacteristicBean.
*
* @param conn
* A connection to the database.
* @param num_samples
* The number of samples in the database.
* @param value
* The value of this sample.
* @return
* @throws SQLException
*/
private static CharacteristicBean getCharacteristicBean(Connection conn, int num_samples, String dbname, Boolean value) throws SQLException {
CharacteristicBean chrbean = new CharacteristicBean();
PreparedStatement getCount;
String querystr = "SELECT `Count` FROM `Count" + dbname + "` WHERE `" + dbname + "`";
if (value != null) {
if (value) {
chrbean.setValue("Yes");
} else {
chrbean.setValue("No");
}
querystr += " = ?;";
getCount = conn.prepareStatement(querystr);
getCount.setBoolean(1, value);
} else {
chrbean.setValue(NO_JAVASCRIPT);
querystr += " IS NULL;";
getCount = conn.prepareStatement(querystr);
}
ResultSet rs = getCount.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
chrbean.setNumOccurrences(count);
chrbean.setInX(((double) num_samples) / ((double) count));
chrbean.setBits(Math.abs(Math.log(chrbean.getInX()) / Math.log(2)));
return chrbean;
}
/**
* Create a CharacteristicBean.
*
* @param conn
* A connection to the database.
* @param num_samples
* The number of samples in the database.
* @param value
* The value of this sample.
* @return
* @throws SQLException
*/
private static CharacteristicBean getCharacteristicBean(Connection conn, int num_samples, String dbname, String value) throws SQLException {
CharacteristicBean chrbean = new CharacteristicBean();
PreparedStatement getCount;
String querystr = "SELECT `Count` FROM `Count" + dbname + "` WHERE `" + dbname + "Hash` = ";
if (value != null) {
querystr += "SHA2(?, 256);";
getCount = conn.prepareStatement(querystr);
getCount.setString(1, value);
chrbean.setValue(StringEscapeUtils.escapeHtml4(value));
} else {
querystr += "'';";
getCount = conn.prepareStatement(querystr);
chrbean.setValue(NO_JAVASCRIPT);
}
try{
ResultSet rs = getCount.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
chrbean.setNumOccurrences(count);
chrbean.setInX(((double) num_samples) / ((double) count));
chrbean.setBits(Math.abs(Math.log(chrbean.getInX()) / Math.log(2)));
}
catch(SQLException ex){
System.out.println("Error is at dbname = " + dbname + " with value " + value);
System.err.println("Error is at dbname = " + dbname + " with value " + value);
throw ex;
}
return chrbean;
}
/**
* Create a CharacteristicBean.
*
* @param conn
* A connection to the database.
* @param num_samples
* The number of samples in the database.
* @param value
* The value of this sample.
* @return
* @throws SQLException
*/
private static CharacteristicBean getCharacteristicBean(Connection conn, int num_samples, String dbname, Integer value) throws SQLException {
CharacteristicBean chrbean = new CharacteristicBean();
PreparedStatement getCount;
String querystr = "SELECT `Count` FROM `Count" + dbname + "` WHERE `" + dbname + "`";
if (value != null) {
chrbean.setValue(value.toString());
querystr += " = ?;";
getCount = conn.prepareStatement(querystr);
getCount.setInt(1, value);
} else {
chrbean.setValue(NO_JAVASCRIPT);
querystr += " IS NULL;";
getCount = conn.prepareStatement(querystr);
}
ResultSet rs = getCount.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
chrbean.setNumOccurrences(count);
chrbean.setInX(((double) num_samples) / ((double) count));
chrbean.setBits(Math.abs(Math.log(chrbean.getInX()) / Math.log(2)));
return chrbean;
}
/**
* Create a CharacteristicBean.
*
* @param conn
* A connection to the database.
* @param num_samples
* The number of samples in the database.
* @param value
* The value of this sample.
* @return
* @throws SQLException
*/
private static CharacteristicBean getCharacteristicBean(Connection conn, int num_samples, String dbname, Long value) throws SQLException {
CharacteristicBean chrbean = new CharacteristicBean();
PreparedStatement getCount;
String querystr = "SELECT `Count` FROM `Count" + dbname + "` WHERE `" + dbname + "`";
if (value != null) {
chrbean.setValue(value.toString());
querystr += " = ?;";
getCount = conn.prepareStatement(querystr);
getCount.setLong(1, value);
} else {
chrbean.setValue(NO_JAVASCRIPT);
querystr += " IS NULL;";
getCount = conn.prepareStatement(querystr);
}
ResultSet rs = getCount.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
chrbean.setNumOccurrences(count);
chrbean.setInX(((double) num_samples) / ((double) count));
chrbean.setBits(Math.abs(Math.log(chrbean.getInX()) / Math.log(2)));
return chrbean;
}
/**
* Create the super cookie CharacteristicBean.
*
* @param conn
* A connection to the database.
* @param num_samples
* The number of samples in the database.
* @param value
* The value of this sample.
* @return
* @throws SQLException
*/
private static CharacteristicBean getSuperCookieCharacteristicBean(Connection conn, int num_samples, Fingerprint fingerprint) throws SQLException {
CharacteristicBean chrbean = new CharacteristicBean();
PreparedStatement getCount;
String querystr = "SELECT `Count` FROM `CountSuperCookie` WHERE"
+ " `SuperCookieLocalStorage`" + (fingerprint.getSuperCookieLocalStorage() == null ? " IS NULL": " = ?")
+ " AND `SuperCookieSessionStorage`" + (fingerprint.getSuperCookieSessionStorage() == null ? " IS NULL": " = ?")
+ " AND `SuperCookieUserData`" + (fingerprint.getSuperCookieUserData() == null ? " IS NULL": " = ?");
getCount = conn.prepareStatement(querystr);
int index = 1;
String superCookieStr = "DOM localStorage: ";
if(fingerprint.getSuperCookieLocalStorage() == null && fingerprint.getSuperCookieSessionStorage() == null && fingerprint.getSuperCookieUserData() == null){
superCookieStr = NO_JAVASCRIPT;
}
else{
if (fingerprint.getSuperCookieLocalStorage() != null) {
if (fingerprint.getSuperCookieLocalStorage()) {
superCookieStr += "Yes";
} else {
superCookieStr += "No";
}
getCount.setBoolean(index, fingerprint.getSuperCookieLocalStorage());
++index;
}
else{
superCookieStr += "NoJS";
}
superCookieStr += ", ";
superCookieStr += "DOM sessionStorage: ";
if (fingerprint.getSuperCookieSessionStorage() != null) {
if (fingerprint.getSuperCookieSessionStorage()) {
superCookieStr += "Yes";
} else {
superCookieStr += "No";
}
getCount.setBoolean(index, fingerprint.getSuperCookieSessionStorage());
++index;
}
else{
superCookieStr += "NoJS";
}
superCookieStr += ", ";
superCookieStr += "IE userData: ";
if (fingerprint.getSuperCookieUserData() != null) {
if (fingerprint.getSuperCookieUserData()) {
superCookieStr += "Yes";
} else {
superCookieStr += "No";
}
getCount.setBoolean(index, fingerprint.getSuperCookieUserData());
++index;
}
else{
superCookieStr += "NoJS";
}
}
chrbean.setValue(superCookieStr);
ResultSet rs = getCount.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
chrbean.setNumOccurrences(count);
chrbean.setInX(((double) num_samples) / ((double) count));
chrbean.setBits(Math.abs(Math.log(chrbean.getInX()) / Math.log(2)));
return chrbean;
}
/**
* Create the ads blocked CharacteristicBean.
*
* @param conn
* A connection to the database.
* @param num_samples
* The number of samples in the database.
* @param value
* The value of this sample.
* @return
* @throws SQLException
*/
private static CharacteristicBean getAdsBlockedCharacteristicBean(Connection conn, int num_samples, Fingerprint fingerprint) throws SQLException {
CharacteristicBean chrbean = new CharacteristicBean();
PreparedStatement getCount;
String querystr = "SELECT `Count` FROM `CountAdsBlocked` WHERE"
+ " `AdsBlockedGoogle`" + (fingerprint.getAdsBlockedGoogle() == null ? " IS NULL": " = ?")
+ " AND `AdsBlockedBanner`" + (fingerprint.getAdsBlockedBanner() == null ? " IS NULL": " = ?")
+ " AND `AdsBlockedScript`" + (fingerprint.getAdsBlockedScript() == null ? " IS NULL": " = ?");
getCount = conn.prepareStatement(querystr);
int index = 1;
String adsBlockedStr = "Google ad: ";
if(fingerprint.getAdsBlockedGoogle() == null && fingerprint.getAdsBlockedBanner() == null && fingerprint.getAdsBlockedScript() == null){
adsBlockedStr = NO_JAVASCRIPT;
}
else{
if (fingerprint.getAdsBlockedGoogle() != null) {
if (fingerprint.getAdsBlockedGoogle()) {
adsBlockedStr += "Blocked";
} else {
adsBlockedStr += "Not blocked";
}
getCount.setBoolean(index, fingerprint.getAdsBlockedGoogle());
++index;
}
else{
adsBlockedStr += "NoJS";
}
adsBlockedStr += ", ";
adsBlockedStr += "Banner ad: ";
if (fingerprint.getAdsBlockedBanner() != null) {
if (fingerprint.getAdsBlockedBanner()) {
adsBlockedStr += "Blocked";
} else {
adsBlockedStr += "Not blocked";
}
getCount.setBoolean(index, fingerprint.getAdsBlockedBanner());
++index;
}
else{
adsBlockedStr += "NoJS";
}
adsBlockedStr += ", ";
adsBlockedStr += "Ad script: ";
if (fingerprint.getAdsBlockedScript() != null) {
if (fingerprint.getAdsBlockedScript()) {
adsBlockedStr += "Blocked";
} else {
adsBlockedStr += "Not blocked";
}
getCount.setBoolean(index, fingerprint.getAdsBlockedScript());
++index;
}
else{
adsBlockedStr += "NoJS";
}
}
chrbean.setValue(adsBlockedStr);
ResultSet rs = getCount.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
chrbean.setNumOccurrences(count);
chrbean.setInX(((double) num_samples) / ((double) count));
chrbean.setBits(Math.abs(Math.log(chrbean.getInX()) / Math.log(2)));
return chrbean;
}
/**
* Create the ads blocked CharacteristicBean.
*
* @param conn
* A connection to the database.
* @param num_samples
* The number of samples in the database.
* @param value
* The value of this sample.
* @return
* @throws SQLException
*/
private static CharacteristicBean getLikeShareCharacteristicBean(Connection conn, int num_samples, Fingerprint fingerprint) throws SQLException {
CharacteristicBean chrbean = new CharacteristicBean();
PreparedStatement getCount;
String querystr = "SELECT `Count` FROM `CountLikeShare` WHERE"
+ " `LikeShareFacebook`" + (fingerprint.getLikeShareFacebook() == null ? " IS NULL": " = ?")
+ " AND `LikeShareTwitter`" + (fingerprint.getLikeShareTwitter() == null ? " IS NULL": " = ?")
+ " AND `LikeShareReddit`" + (fingerprint.getLikeShareReddit() == null ? " IS NULL": " = ?");
getCount = conn.prepareStatement(querystr);
int index = 1;
String likeShareBlockedStr = "Facebook button: ";
if(fingerprint.getLikeShareFacebook() == null && fingerprint.getLikeShareTwitter() == null && fingerprint.getLikeShareReddit() == null){
likeShareBlockedStr = NO_JAVASCRIPT;
}
else{
if (fingerprint.getLikeShareFacebook() != null) {
if (fingerprint.getLikeShareFacebook() == 1) {
likeShareBlockedStr += "Replaced by Privacy Badger or similar";
} else if(fingerprint.getLikeShareFacebook() == 2) {
likeShareBlockedStr += "Blocked by script blocker";
}
else if(fingerprint.getLikeShareFacebook() == 3){
likeShareBlockedStr += "Blocked by Adblock Plus Anti-Social list or similar";
}
else if(fingerprint.getLikeShareFacebook() == 0){
likeShareBlockedStr += "Not blocked";
}
else{
likeShareBlockedStr += "Invalid";
}
getCount.setInt(index, fingerprint.getLikeShareFacebook());
++index;
}
else{
likeShareBlockedStr += "NoJS";
}
likeShareBlockedStr += ",<br/>\n";
likeShareBlockedStr += "Twitter share button: ";
if (fingerprint.getLikeShareTwitter() != null) {
if (fingerprint.getLikeShareTwitter() == 1) {
likeShareBlockedStr += "Replaced by Privacy Badger or similar";
}
else if (fingerprint.getLikeShareTwitter() == 2) {
likeShareBlockedStr += "Blocked by script blocker or Adblock Plus Anti-Social list or similar";
}
else if (fingerprint.getLikeShareTwitter() == 0) {
likeShareBlockedStr += "Not blocked";
}
else {
likeShareBlockedStr += "Invalid";
}
getCount.setInt(index, fingerprint.getLikeShareTwitter());
++index;
}
else{
likeShareBlockedStr += "NoJS";
}
likeShareBlockedStr += ",<br/>\n";
likeShareBlockedStr += "Reddit button: ";
if (fingerprint.getLikeShareReddit() != null) {
if (fingerprint.getLikeShareReddit() == 2) {
likeShareBlockedStr += "Blocked by script blocker or Adblock Plus Anti-Social list or similar";
}
else if (fingerprint.getLikeShareReddit() == 4) {
likeShareBlockedStr += "Blocked by unknown";
}
else if (fingerprint.getLikeShareReddit() == 0) {
likeShareBlockedStr += "Not blocked";
}
else {
likeShareBlockedStr += "Invalid";
}
getCount.setInt(index, fingerprint.getLikeShareReddit());
++index;
}
else{
likeShareBlockedStr += "NoJS";
}
}
chrbean.setValue(likeShareBlockedStr);
ResultSet rs = getCount.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
chrbean.setNumOccurrences(count);
chrbean.setInX(((double) num_samples) / ((double) count));
chrbean.setBits(Math.abs(Math.log(chrbean.getInX()) / Math.log(2)));
return chrbean;
}
/**
* Create the touch CharacteristicBean.
*
* @param conn
* A connection to the database.
* @param num_samples
* The number of samples in the database.
* @param value
* The value of this sample.
* @return
* @throws SQLException
*/
private static CharacteristicBean getTouchCharacteristicBean(Connection conn, int num_samples, Fingerprint fingerprint) throws SQLException {
CharacteristicBean chrbean = new CharacteristicBean();
PreparedStatement getCount;
String querystr = "SELECT `Count` FROM `CountTouchDetails` WHERE"
+ " `TouchPoints`" + (fingerprint.getTouchPoints() == null ? " IS NULL": " = ?")
+ " AND `TouchEvent`" + (fingerprint.getTouchEvent() == null ? " IS NULL": " = ?")
+ " AND `TouchStart`" + (fingerprint.getTouchStart() == null ? " IS NULL": " = ?");
getCount = conn.prepareStatement(querystr);
int index = 1;
String touchStr;
if(fingerprint.getTouchPoints() == null && fingerprint.getTouchEvent() == null && fingerprint.getTouchStart() == null){
touchStr = NO_JAVASCRIPT;
}
else{
touchStr = "Max touchpoints: ";
if(fingerprint.getTouchPoints() != null){
touchStr += fingerprint.getTouchPoints();
getCount.setInt(index, fingerprint.getTouchPoints());
++index;
}
else{
touchStr += "NoJS";
}
touchStr += "; TouchEvent supported: ";
if(fingerprint.getTouchEvent() != null){
touchStr += fingerprint.getTouchEvent();
getCount.setBoolean(index, fingerprint.getTouchEvent());
++index;
}
else{
touchStr += "NoJS";
}
touchStr += "; onTouchStart supported: ";
if(fingerprint.getTouchStart() != null){
touchStr += fingerprint.getTouchStart();
getCount.setBoolean(index, fingerprint.getTouchStart());
++index;
}
else{
touchStr += "NoJS";
}
}
chrbean.setValue(touchStr);
ResultSet rs = getCount.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
chrbean.setNumOccurrences(count);
chrbean.setInX(((double) num_samples) / ((double) count));
chrbean.setBits(Math.abs(Math.log(chrbean.getInX()) / Math.log(2)));
return chrbean;
}
/**
* Create the audio fingerprint CharacteristicBean.
*
* @param conn
* A connection to the database.
* @param num_samples
* The number of samples in the database.
* @param value
* The value of this sample.
* @return
* @throws SQLException
*/
private static CharacteristicBean getAudioTestsCharacteristicBean(Connection conn, int num_samples, Fingerprint fingerprint) throws SQLException {
CharacteristicBean chrbean = new CharacteristicBean();
PreparedStatement getCount;
String querystr = "SELECT `Count` FROM `CountAudioFingerprint` WHERE"
+ " `AudioFingerprintHash` = SHA2(CONCAT_WS('', ?, ?, ?, ?, ?), 256);";
getCount = conn.prepareStatement(querystr);
int index = 1;
String touchStr;
if(fingerprint.getAudioFingerprintPXI() == null && fingerprint.getAudioFingerprintPXIFullBuffer() == null && fingerprint.getAudioFingerprintNtVc() == null
&& fingerprint.getAudioFingerprintCC() == null && fingerprint.getAudioFingerprintHybrid() == null){
touchStr = NO_JAVASCRIPT;
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
}
else{
touchStr = "<b>Fingerprint using DynamicsCompressor (sum of buffer values):</b><br> ";
if(fingerprint.getAudioFingerprintPXI() != null){
touchStr += fingerprint.getAudioFingerprintPXI();
getCount.setString(index, fingerprint.getAudioFingerprintPXI());
++index;
}
else{
touchStr += "NoJS";
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
}
touchStr += "<br><b>Fingerprint using DynamicsCompressor (hash of full buffer):</b><br> ";
if(fingerprint.getAudioFingerprintPXIFullBuffer() != null){
touchStr += fingerprint.getAudioFingerprintPXIFullBuffer();
getCount.setString(index, fingerprint.getAudioFingerprintPXIFullBuffer());
++index;
}
else{
touchStr += "NoJS";
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
}
touchStr += "<br><b>AudioContext properties:</b><br> ";
if(fingerprint.getAudioFingerprintNtVc() != null){
touchStr += fingerprint.getAudioFingerprintNtVc();
getCount.setString(index, fingerprint.getAudioFingerprintNtVc());
++index;
}
else{
touchStr += "NoJS";
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
}
touchStr += "<br><b>Fingerprint using OscillatorNode:</b><br> ";
if(fingerprint.getAudioFingerprintCC() != null){
touchStr += fingerprint.getAudioFingerprintCC();
getCount.setString(index, fingerprint.getAudioFingerprintCC());
++index;
}
else{
touchStr += "NoJS";
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
}
touchStr += "<br><b>Fingerprint using hybrid of OscillatorNode/DynamicsCompressor method:</b><br> ";
if(fingerprint.getAudioFingerprintHybrid() != null){
touchStr += fingerprint.getAudioFingerprintHybrid();
getCount.setString(index, fingerprint.getAudioFingerprintHybrid());
++index;
}
else{
touchStr += "NoJS";
getCount.setNull(index, java.sql.Types.VARCHAR);
++index;
}
}
chrbean.setValue(touchStr);
ResultSet rs = getCount.executeQuery();
rs.next();
int count = rs.getInt(1);
rs.close();
chrbean.setNumOccurrences(count);
chrbean.setInX(((double) num_samples) / ((double) count));
chrbean.setBits(Math.abs(Math.log(chrbean.getInX()) / Math.log(2)));
return chrbean;
}
public static HistoryListBean getSampleSetIDsHistory(String sampleSetID, ServletContext context) throws ServletException {
HistoryListBean history = new HistoryListBean();
Connection conn = null;
try {
conn = Database.getConnection();
conn.setReadOnly(true);
if (sampleSetID == null) {
/*
* No sampleSetID means no history.
*/
return history;
}
PreparedStatement getHistory = conn.prepareStatement(selectSampleSetIDHistory);
getHistory.setString(1, sampleSetID);
ResultSet rs = getHistory.executeQuery();
SimpleDateFormat dateformat = new SimpleDateFormat("dd/MM/yyyy, HH:mm:ss z");
dateformat.setTimeZone(TimeZone.getTimeZone("UTC"));
while (rs.next()) {
Timestamp timestamp = rs.getTimestamp(2);
history.addHistoryBean(new HistoryBean(rs.getString(1), dateformat.format(timestamp)));
}
rs.close();
getHistory.close();
conn.close();
} catch (SQLException e) {
e.printStackTrace();
} finally {
// Close the connection
// Finally triggers even if we return
if (conn != null) {
try {
conn.close();
} catch (SQLException e) {
// Ignore
}
}
}
return history;
}
private static Fingerprint getFingerprintFromSampleID(Connection conn, String sampleUUID) throws SQLException {
PreparedStatement getFingerprint = conn.prepareStatement(selectSampleStr);
getFingerprint.setString(1, sampleUUID);
ResultSet rs = getFingerprint.executeQuery();
if (!rs.next()) {
// No sample found with that sampleID.
return null;
}
Fingerprint fingerprint = new Fingerprint();
int index = 1;
// AllHeaders
fingerprint.setAllHeaders(rs.getString(index));
++index;
// ColourVision
fingerprint.setContrastLevel(rs.getInt(index));
++index;
// UserAgent
fingerprint.setUser_agent(rs.getString(index));
++index;
// AcceptHeaders
fingerprint.setAccept_headers(rs.getString(index));
++index;
// Platform
fingerprint.setPlatform(rs.getString(index));
++index;
// PlatformFlash
fingerprint.setPlatformFlash(rs.getString(index));
++index;
// PluginDetails
fingerprint.setPluginDetails(rs.getString(index));
++index;
// TimeZone
fingerprint.setTimeZone(rs.getString(index));
++index;
// ScreenDetails
fingerprint.setScreenDetails(rs.getString(index));
++index;
// ScreenDetailsFlash
fingerprint.setScreenDetailsFlash(rs.getString(index));
++index;
// ScreenDetailsCSS
fingerprint.setScreenDetailsCSS(rs.getString(index));
++index;
// LanguageFlash
fingerprint.setLanguageFlash(rs.getString(index));
++index;
// Fonts
fingerprint.setFonts(rs.getString(index));
++index;
// FontsJS_CSS
fingerprint.setFontsJS_CSS(rs.getString(index));
++index;
// FontsCSS
fingerprint.setFontsCSS(rs.getString(index));
++index;
// CharSizes
fingerprint.setCharSizes(rs.getString(index));
++index;
// CookiesEnabled
fingerprint.setCookiesEnabled(rs.getBoolean(index));
++index;
// SuperCookieLocalStorage
fingerprint.setSuperCookieLocalStorage(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setSuperCookieLocalStorage(null);
}
++index;
// SuperCookieSessionStorage
fingerprint.setSuperCookieSessionStorage(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setSuperCookieSessionStorage(null);
}
++index;
// SuperCookieUserData
fingerprint.setSuperCookieUserData(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setSuperCookieUserData(null);
}
++index;
// HstsEnabled
fingerprint.setHstsEnabled(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setHstsEnabled(null);
}
++index;
// IndexedDBEnabled
fingerprint.setIndexedDBEnabled(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setIndexedDBEnabled(null);
}
++index;
// DoNotTrack
fingerprint.setDoNotTrack(rs.getString(index));
++index;
// ClockDifference
fingerprint.setClockDifference(rs.getLong(index));
if (rs.wasNull()) {
fingerprint.setClockDifference(null);
}
++index;
// DateTime
fingerprint.setDateTime(rs.getString(index));
++index;
// MathTan
fingerprint.setMathTan(rs.getString(index));
++index;
// UsingTor
fingerprint.setUsingTor(rs.getBoolean(index));
++index;
// TbbVersion
fingerprint.setTbbVersion(rs.getString(index));
++index;
// AdsBlockedGoogle
fingerprint.setAdsBlockedGoogle(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setAdsBlockedGoogle(null);
}
++index;
// AdsBlockedBanner
fingerprint.setAdsBlockedBanner(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setAdsBlockedBanner(null);
}
++index;
// AdsBlockedScript
fingerprint.setAdsBlockedScript(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setAdsBlockedScript(null);
}
++index;
// LikeShareFacebook
fingerprint.setLikeShareFacebook(rs.getInt(index));
if (rs.wasNull()) {
fingerprint.setLikeShareFacebook(null);
}
++index;
// LikeShareTwitter
fingerprint.setLikeShareTwitter(rs.getInt(index));
if (rs.wasNull()) {
fingerprint.setLikeShareTwitter(null);
}
++index;
// LikeShareReddit
fingerprint.setLikeShareReddit(rs.getInt(index));
if (rs.wasNull()) {
fingerprint.setLikeShareReddit(null);
}
++index;
// Canvas
fingerprint.setCanvas(rs.getString(index));
++index;
// WebGLVendor
fingerprint.setWebGLVendor(rs.getString(index));
++index;
// WebGLRenderer
fingerprint.setWebGLRenderer(rs.getString(index));
++index;
// TouchPoints
fingerprint.setTouchPoints(rs.getInt(index));
if (rs.wasNull()) {
fingerprint.setTouchPoints(null);
}
++index;
// TouchEvent
fingerprint.setTouchEvent(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setTouchEvent(null);
}
++index;
// TouchStart
fingerprint.setTouchStart(rs.getBoolean(index));
if (rs.wasNull()) {
fingerprint.setTouchStart(null);
}
++index;
// AudioFingerprintPXI
fingerprint.setAudioFingerprintPXI(rs.getString(index));
++index;
// AudioFingerprintPXIFullBuffer
fingerprint.setAudioFingerprintPXIFullBuffer(rs.getString(index));
++index;
// AudioFingerprintNtVc
fingerprint.setAudioFingerprintNtVc(rs.getString(index));
++index;
// AudioFingerprintCC
fingerprint.setAudioFingerprintCC(rs.getString(index));
++index;
// AudioFingerprintHybrid
fingerprint.setAudioFingerprintHybrid(rs.getString(index));
++index;
rs.close();
getFingerprint.close();
return fingerprint;
}
}
class VersionCount implements Comparable<VersionCount>{
private int version;
private int count;
public VersionCount(int version){
this.version = version;
this.count = 0;
}
public VersionCount(int version, int count){
this.version = version;
this.count = count;
}
@Override
public int compareTo(VersionCount o) {
return this.version - o.version;
}
public int getVersion() {
return version;
}
public void setVersion(int version) {
this.version = version;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}