package oasis.web.utils;

import java.nio.charset.StandardCharsets;

import javax.ws.rs.container.ContainerRequestContext;
import javax.ws.rs.core.HttpHeaders;

import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;

public class UserAgentFingerprinter {

  // Let's use the fastest algorithm to start with. Maybe switch to MurMur3 later for better uniqueness.
  private static final HashFunction HASH_FUNCTION = Hashing.adler32();

  private static final String[] FINGERPRINTED_HEADERS = {
      HttpHeaders.USER_AGENT,
      HttpHeaders.ACCEPT_CHARSET,
      HttpHeaders.ACCEPT_ENCODING,
      HttpHeaders.ACCEPT_LANGUAGE,
  };

  public byte[] fingerprint(ContainerRequestContext ctx) {
    Hasher hasher = HASH_FUNCTION.newHasher();
    for (String name : FINGERPRINTED_HEADERS) {
      putHeader(hasher, name, ctx.getHeaderString(name));
    }
    return hasher.hash().asBytes();
  }

  public byte[] fingerprint(HttpHeaders ctx) {
    Hasher hasher = HASH_FUNCTION.newHasher();
    for (String name : FINGERPRINTED_HEADERS) {
      putHeader(hasher, name, ctx.getHeaderString(name));
    }
    return hasher.hash().asBytes();
  }

  private void putHeader(Hasher hasher, String name, String value) {
    // willfully use == rather than equals() because we know we're using the interned constant here
    //noinspection StringEquality
    if (name == HttpHeaders.ACCEPT_ENCODING) {
      // special-case for Chrome, which sends an "sdch" accept-encoding randomly [1],
      // but never sends it on POST requests [2] (e.g. when submitting the login form
      // and creating a SidToken). The Accept-Encoding value is hard-coded [3] so we
      // simply strip ",sdch" before hashing the value (no need for complicated
      // algorithms like splitting on commas before reassembling the header value).
      //
      // [1] https://chromium.googlesource.com/chromium/src/+/37.0.2062.94/net/url_request/url_request_http_job.cc#487
      // [2] https://chromium.googlesource.com/chromium/src/+/37.0.2062.94/net/url_request/url_request_http_job.cc#470
      // [3] https://chromium.googlesource.com/chromium/src/+/37.0.2062.94/net/url_request/url_request_http_job.cc#512
      value = value.replace(",sdch", "");
    }
    if (value != null) {
      hasher.putString(value, StandardCharsets.UTF_8);
    }
  }
}

@@ -1261,24 +1261,51 @@
 ng value) %7B%0A
+    if (value != null) %7B%0A  
     // willf
@@ -1392,16 +1392,18 @@
 nt here%0A
+  
     //no
@@ -1428,24 +1428,26 @@
 quality%0A    
+  
 if (name == 
@@ -1473,24 +1473,26 @@
 ENCODING) %7B%0A
+  
       // spe
@@ -1571,16 +1571,18 @@
 ,%0A      
+ 
+ 
 // but n
@@ -1655,24 +1655,26 @@
  form%0A      
+  
 // and creat
@@ -1742,24 +1742,26 @@
 so we%0A      
+ 
+ 
 // simply st
@@ -1822,16 +1822,18 @@
 licated%0A
+  
       //
@@ -1908,16 +1908,18 @@
 value).%0A
+  
       //
@@ -1921,24 +1921,26 @@
    //%0A      
+  
 // %5B1%5D https
@@ -2040,16 +2040,18 @@
 .cc#487%0A
+  
       //
@@ -2163,24 +2163,26 @@
 c#470%0A      
+ 
+ 
 // %5B3%5D https
@@ -2288,16 +2288,18 @@
 2%0A      
+  
 value = 
@@ -2334,34 +2334,11 @@
     
-%7D%0A    if (value != null) %7B
+  %7D
 %0A   

Fix NPE in UserAgentFingerprinter when there's no Accept-Encoding header