[epiphany/wip/google-safe-browsing: 12/12] gsb-service: Implement URL verification logic



commit d04ab62eccb6483ec50e7ecff839594ec9e4dd0a
Author: Gabriel Ivascu <gabrielivascu gnome org>
Date:   Mon Sep 18 20:18:53 2017 +0300

    gsb-service: Implement URL verification logic

 lib/safe-browsing/ephy-gsb-service.c |  450 +++++++++++++++++++++++++---------
 lib/safe-browsing/ephy-gsb-service.h |   14 +-
 lib/safe-browsing/ephy-gsb-storage.c |    6 +-
 lib/safe-browsing/ephy-gsb-utils.c   |   54 ++++
 lib/safe-browsing/ephy-gsb-utils.h   |    6 +
 5 files changed, 403 insertions(+), 127 deletions(-)
---
diff --git a/lib/safe-browsing/ephy-gsb-service.c b/lib/safe-browsing/ephy-gsb-service.c
index 8f18355..564600d 100644
--- a/lib/safe-browsing/ephy-gsb-service.c
+++ b/lib/safe-browsing/ephy-gsb-service.c
@@ -58,22 +58,41 @@ static GParamSpec *obj_properties[LAST_PROP];
 static gboolean ephy_gsb_service_update_db (EphyGSBService *self);
 
 typedef struct {
-  EphyGSBService *service;
-  GList          *prefixes;
+  EphyGSBService                  *service;
+  GHashTable                      *threats;
+  GList                           *matching_prefixes;
+  GList                           *matching_hashes;
+  EphyGSBServiceVerifyURLCallback  callback;
+  gpointer                         user_data;
 } UpdateFullHashesAsyncData;
 
 static UpdateFullHashesAsyncData *
-update_full_hashes_async_data_new (EphyGSBService *service,
-                                   GList          *prefixes)
+update_full_hashes_async_data_new (EphyGSBService                  *service,
+                                   GHashTable                      *threats,
+                                   GList                           *matching_prefixes,
+                                   GList                           *matching_hashes,
+                                   EphyGSBServiceVerifyURLCallback  callback,
+                                   gpointer                         user_data)
 {
   UpdateFullHashesAsyncData *data;
 
   g_assert (EPHY_IS_GSB_SERVICE (service));
-  g_assert (prefixes);
+  g_assert (threats);
+  g_assert (matching_prefixes);
+  g_assert (matching_hashes);
+  g_assert (callback);
 
   data = g_slice_new (UpdateFullHashesAsyncData);
   data->service = g_object_ref (service);
-  data->prefixes = g_list_copy_deep (prefixes, (GCopyFunc)g_bytes_ref, NULL);
+  data->threats = g_hash_table_ref (threats);
+  data->matching_prefixes = g_list_copy_deep (matching_prefixes,
+                                              (GCopyFunc)g_bytes_ref,
+                                              NULL);
+  data->matching_hashes = g_list_copy_deep (matching_hashes,
+                                            (GCopyFunc)g_bytes_ref,
+                                            NULL);
+  data->callback = callback;
+  data->user_data = user_data;
 
   return data;
 }
@@ -84,7 +103,9 @@ update_full_hashes_async_data_free (UpdateFullHashesAsyncData *data)
   g_assert (data);
 
   g_object_unref (data->service);
-  g_list_free_full (data->prefixes, (GDestroyNotify)g_bytes_unref);
+  g_hash_table_unref (data->threats);
+  g_list_free_full (data->matching_prefixes, (GDestroyNotify)g_bytes_unref);
+  g_list_free_full (data->matching_hashes, (GDestroyNotify)g_bytes_unref);
   g_slice_free (UpdateFullHashesAsyncData, data);
 }
 
@@ -291,120 +312,6 @@ ephy_gsb_service_update_db (EphyGSBService *self)
 }
 
 static void
-ephy_gsb_service_update_full_hashes_cb (SoupSession *session,
-                                        SoupMessage *msg,
-                                        gpointer     user_data)
-{
-  UpdateFullHashesAsyncData *data = user_data;
-  JsonNode *body_node;
-  JsonObject *body_obj;
-  JsonArray *matches;
-  GError *error = NULL;
-  const char *negative_duration;
-  double duration;
-
-  if (msg->status_code != 200) {
-    LOG ("Cannot update full hashes. Server responded: %u, %s",
-         msg->status_code, msg->response_body->data);
-    return;
-  }
-
-  body_node = json_from_string (msg->response_body->data, &error);
-  if (error) {
-    LOG ("Cannot update full hashes. Response is not a valid JSON: %s", error->message);
-    g_error_free (error);
-    return;
-  }
-
-  body_obj = json_node_get_object (body_node);
-  matches = json_object_get_array_member (body_obj, "matches");
-
-  for (guint i = 0; i < json_array_get_length (matches); i++) {
-    EphyGSBThreatList *list;
-    JsonObject *match = json_array_get_object_element (matches, i);
-    const char *threat_type = json_object_get_string_member (match, "threatType");
-    const char *platform_type = json_object_get_string_member (match, "platformType");
-    const char *threat_entry_type = json_object_get_string_member (match, "threatEntryType");
-    JsonObject *threat = json_object_get_object_member (match, "threat");
-    const char *hash_b64 = json_object_get_string_member (threat, "hash");
-    const char *positive_duration;
-    char *malware_threat_type = NULL;
-    guint8 *hash;
-    gsize length;
-
-    list = ephy_gsb_threat_list_new (threat_type, platform_type, threat_entry_type, NULL, 0);
-    hash = g_base64_decode (hash_b64, &length);
-    positive_duration = json_object_get_string_member (match, "cacheDuration");
-    sscanf (positive_duration, "%lfs", &duration);
-
-    if (json_object_has_non_null_object_member (match, "threatEntryMetadata")) {
-      JsonObject *threat_entry_metadata = json_object_get_object_member (match, "threatEntryMetadata");
-      malware_threat_type = ephy_gsb_utils_get_metadata_entry (threat_entry_metadata, "malware_threat_type");
-    }
-
-    ephy_gsb_storage_insert_full_hash (data->service->storage,
-                                       list, hash,
-                                       floor (duration),
-                                       malware_threat_type);
-
-    g_free (hash);
-    g_free (malware_threat_type);
-    ephy_gsb_threat_list_free (list);
-  }
-
-  /* Update negative cache duration. */
-  negative_duration = json_object_get_string_member (body_obj, "negativeCacheDuration");
-  sscanf (negative_duration, "%lfs", &duration);
-  for (GList *l = data->prefixes; l && l->data; l = l->next) {
-    ephy_gsb_storage_update_hash_prefix_expiration (data->service->storage,
-                                                    l->data,
-                                                    floor (duration));
-  }
-
-  /* TODO: Handle minimumWaitDuration. */
-
-  json_node_unref (body_node);
-  update_full_hashes_async_data_free (data);
-}
-
-static void
-ephy_gsb_service_update_full_hashes (EphyGSBService *self,
-                                     GList          *prefixes)
-{
-  SoupMessage *msg;
-  GList *threat_lists;
-  JsonNode *body_node;
-  JsonObject *body_obj;
-  char *url;
-  char *body;
-
-  g_assert (EPHY_IS_GSB_SERVICE (self));
-  g_assert (ephy_gsb_storage_is_operable (self->storage));
-  g_assert (prefixes);
-
-  LOG ("Updating full hashes of %u prefixes", g_list_length (prefixes));
-
-  threat_lists = ephy_gsb_storage_get_threat_lists (self->storage);
-  body_obj = ephy_gsb_utils_make_full_hashes_request (threat_lists, prefixes);
-
-  body_node = json_node_new (JSON_NODE_OBJECT);
-  json_node_set_object (body_node, body_obj);
-  body = json_to_string (body_node, TRUE);
-
-  url = g_strdup_printf ("%sfullHashes:find?key=%s", API_PREFIX, self->api_key);
-  msg = soup_message_new (SOUP_METHOD_POST, url);
-  soup_message_set_request (msg, "application/json", SOUP_MEMORY_TAKE, body, strlen (body));
-  soup_session_queue_message (self->session, msg,
-                              ephy_gsb_service_update_full_hashes_cb,
-                              update_full_hashes_async_data_new (self, prefixes));
-
-  g_free (url);
-  json_object_unref (body_obj);
-  json_node_unref (body_node);
-  g_list_free_full (threat_lists, (GDestroyNotify)ephy_gsb_threat_list_free);
-}
-
-static void
 ephy_gsb_service_set_property (GObject      *object,
                                guint         prop_id,
                                const GValue *value,
@@ -543,3 +450,304 @@ ephy_gsb_service_new (const char *api_key,
 
   return service;
 }
+
+static void
+ephy_gsb_service_update_full_hashes_cb (SoupSession *session,
+                                        SoupMessage *msg,
+                                        gpointer     user_data)
+{
+  UpdateFullHashesAsyncData *data = (UpdateFullHashesAsyncData *)user_data;
+  JsonNode *body_node;
+  JsonObject *body_obj;
+  JsonArray *matches;
+  GList *hashes_lookup = NULL;
+  GError *error = NULL;
+  const char *negative_duration;
+  double duration;
+
+  if (msg->status_code != 200) {
+    LOG ("Cannot update full hashes. Server responded: %u, %s",
+         msg->status_code, msg->response_body->data);
+    goto out;
+  }
+
+  body_node = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    LOG ("Cannot update full hashes. Response is not a valid JSON: %s", error->message);
+    g_error_free (error);
+    goto out;
+  }
+
+  body_obj = json_node_get_object (body_node);
+  matches = json_object_get_array_member (body_obj, "matches");
+
+  /* Update full hashes in database. */
+  for (guint i = 0; i < json_array_get_length (matches); i++) {
+    EphyGSBThreatList *list;
+    JsonObject *match = json_array_get_object_element (matches, i);
+    const char *threat_type = json_object_get_string_member (match, "threatType");
+    const char *platform_type = json_object_get_string_member (match, "platformType");
+    const char *threat_entry_type = json_object_get_string_member (match, "threatEntryType");
+    JsonObject *threat = json_object_get_object_member (match, "threat");
+    const char *hash_b64 = json_object_get_string_member (threat, "hash");
+    const char *positive_duration;
+    char *malware_threat_type = NULL;
+    guint8 *hash;
+    gsize length;
+
+    list = ephy_gsb_threat_list_new (threat_type, platform_type, threat_entry_type, NULL, 0);
+    hash = g_base64_decode (hash_b64, &length);
+    positive_duration = json_object_get_string_member (match, "cacheDuration");
+    sscanf (positive_duration, "%lfs", &duration);
+
+    if (json_object_has_non_null_object_member (match, "threatEntryMetadata")) {
+      JsonObject *threat_entry_metadata = json_object_get_object_member (match, "threatEntryMetadata");
+      malware_threat_type = ephy_gsb_utils_get_metadata_entry (threat_entry_metadata, "malware_threat_type");
+    }
+
+    ephy_gsb_storage_insert_full_hash (data->service->storage,
+                                       list, hash,
+                                       floor (duration),
+                                       malware_threat_type);
+
+    g_free (hash);
+    g_free (malware_threat_type);
+    ephy_gsb_threat_list_free (list);
+  }
+
+  /* Update negative cache duration. */
+  negative_duration = json_object_get_string_member (body_obj, "negativeCacheDuration");
+  sscanf (negative_duration, "%lfs", &duration);
+  for (GList *l = data->matching_prefixes; l && l->data; l = l->next) {
+    ephy_gsb_storage_update_hash_prefix_expiration (data->service->storage,
+                                                    l->data,
+                                                    floor (duration));
+  }
+
+  /* TODO: Handle minimumWaitDuration. */
+
+  /* Repeat the full hash verification. */
+  hashes_lookup = ephy_gsb_storage_lookup_full_hashes (data->service->storage,
+                                                       data->matching_hashes);
+  for (GList *l = hashes_lookup; l && l->data; l = l->next) {
+    EphyGSBHashFullLookup *lookup = (EphyGSBHashFullLookup *)l->data;
+    EphyGSBThreatList *list;
+
+    if (!lookup->expired) {
+      list = ephy_gsb_threat_list_new (lookup->threat_type,
+                                       lookup->platform_type,
+                                       lookup->threat_entry_type,
+                                       NULL, 0);
+      g_hash_table_add (data->threats, list);
+    }
+  }
+
+out:
+  data->callback (data->threats, data->user_data);
+
+  if (body_node)
+    json_node_unref (body_node);
+  if (hashes_lookup)
+    g_list_free_full (hashes_lookup, (GDestroyNotify)ephy_gsb_hash_full_lookup_free);
+  update_full_hashes_async_data_free (data);
+}
+
+static void
+ephy_gsb_service_update_full_hashes (EphyGSBService                  *self,
+                                     GHashTable                      *threats,
+                                     GList                           *matching_prefixes,
+                                     GList                           *matching_hashes,
+                                     EphyGSBServiceVerifyURLCallback  callback,
+                                     gpointer                         user_data)
+{
+  UpdateFullHashesAsyncData *data;
+  SoupMessage *msg;
+  GList *threat_lists;
+  JsonNode *body_node;
+  JsonObject *body_obj;
+  char *url;
+  char *body;
+
+  g_assert (EPHY_IS_GSB_SERVICE (self));
+  g_assert (ephy_gsb_storage_is_operable (self->storage));
+  g_assert (threats);
+  g_assert (matching_prefixes);
+  g_assert (matching_hashes);
+  g_assert (callback);
+
+  LOG ("Updating full hashes for %u prefixes", g_list_length (matching_prefixes));
+
+  threat_lists = ephy_gsb_storage_get_threat_lists (self->storage);
+  body_obj = ephy_gsb_utils_make_full_hashes_request (threat_lists, matching_prefixes);
+
+  body_node = json_node_new (JSON_NODE_OBJECT);
+  json_node_set_object (body_node, body_obj);
+  body = json_to_string (body_node, TRUE);
+
+  url = g_strdup_printf ("%sfullHashes:find?key=%s", API_PREFIX, self->api_key);
+  msg = soup_message_new (SOUP_METHOD_POST, url);
+  soup_message_set_request (msg, "application/json", SOUP_MEMORY_TAKE, body, strlen (body));
+
+  data = update_full_hashes_async_data_new (self, threats,
+                                            matching_prefixes, matching_hashes,
+                                            callback, user_data);
+  soup_session_queue_message (self->session, msg,
+                              ephy_gsb_service_update_full_hashes_cb, data);
+
+  g_free (url);
+  json_object_unref (body_obj);
+  json_node_unref (body_node);
+  g_list_free_full (threat_lists, (GDestroyNotify)ephy_gsb_threat_list_free);
+}
+
+static void
+ephy_gsb_service_verify_hashes (EphyGSBService                  *self,
+                                GList                           *hashes,
+                                GHashTable                      *threats,
+                                EphyGSBServiceVerifyURLCallback  callback,
+                                gpointer                         user_data)
+{
+  GList *cues;
+  GList *prefixes_lookup = NULL;
+  GList *hashes_lookup = NULL;
+  GList *matching_prefixes = NULL;
+  GList *matching_hashes = NULL;
+  GHashTable *matching_prefixes_set;
+  GHashTable *matching_hashes_set;
+  GHashTableIter iter;
+  gpointer value;
+  gboolean has_matching_expired_hashes = FALSE;
+  gboolean has_matching_expired_prefixes = FALSE;
+
+  g_assert (EPHY_IS_GSB_SERVICE (self));
+  g_assert (ephy_gsb_storage_is_operable (self->storage));
+  g_assert (threats);
+  g_assert (hashes);
+  g_assert (callback);
+
+  matching_prefixes_set = g_hash_table_new (g_bytes_hash, g_bytes_equal);
+  matching_hashes_set = g_hash_table_new (g_bytes_hash, g_bytes_equal);
+
+  /* Check for hash prefixes in database that match any of the full hashes. */
+  cues = ephy_gsb_utils_get_hash_cues (hashes);
+  prefixes_lookup = ephy_gsb_storage_lookup_hash_prefixes (self->storage, cues);
+  for (GList *p = prefixes_lookup; p && p->data; p = p->next) {
+    EphyGSBHashPrefixLookup *lookup = (EphyGSBHashPrefixLookup *)p->data;
+
+    for (GList *h = hashes; h && h->data; h = h->next) {
+      if (ephy_gsb_utils_hash_has_prefix (h->data, lookup->prefix)) {
+        value = g_hash_table_lookup (matching_prefixes_set, lookup->prefix);
+
+        /* Consider the prefix expired if it's expired in at least one threat list. */
+        g_hash_table_replace (matching_prefixes_set,
+                              lookup->prefix,
+                              GINT_TO_POINTER (GPOINTER_TO_INT (value) || lookup->negative_expired));
+        g_hash_table_add (matching_hashes_set, h->data);
+      }
+    }
+  }
+
+  /* If there are no database matches, then the URL is safe. */
+  if (g_hash_table_size (matching_hashes_set) == 0) {
+    LOG ("No database match, URL is safe");
+    goto return_result;
+  }
+
+  /* Check for full hashes matches.
+   * All unexpired full hash matches are added directly to the result set. */
+  matching_hashes = g_hash_table_get_keys (matching_hashes_set);
+  hashes_lookup = ephy_gsb_storage_lookup_full_hashes (self->storage, matching_hashes);
+  for (GList *l = hashes_lookup; l && l->data; l = l->next) {
+    EphyGSBHashFullLookup *lookup = (EphyGSBHashFullLookup *)l->data;
+    EphyGSBThreatList *list;
+
+    if (lookup->expired) {
+      has_matching_expired_hashes = TRUE;
+    } else {
+      list = ephy_gsb_threat_list_new (lookup->threat_type,
+                                       lookup->platform_type,
+                                       lookup->threat_entry_type,
+                                       NULL, 0);
+      g_hash_table_add (threats, list);
+    }
+  }
+
+  /* Check for positive cache hit.
+   * That is, there is at least one unexpired full hash match. */
+  if (g_hash_table_size (threats) > 0) {
+    LOG ("Positive cache hit, URL is not safe");
+    goto return_result;
+  }
+
+  /* Check for negative cache hit. That is, there are no expired
+   * full hash matches and all hash prefix matches are negative-unexpired. */
+  g_hash_table_iter_init (&iter, matching_prefixes_set);
+  while (g_hash_table_iter_next (&iter, NULL, &value)) {
+    if (GPOINTER_TO_INT (value) == TRUE) {
+      has_matching_expired_prefixes = TRUE;
+      break;
+    }
+  }
+  if (!has_matching_expired_hashes && !has_matching_expired_prefixes) {
+    LOG ("Negative cache hit, URL is safe");
+    goto return_result;
+  }
+
+  /* At this point we have either expired full hash matches and/or
+   * negative-expired hash prefix matches, so we need to find from
+   * the server whether the URL is safe or not. We do this by updating
+   * the full hashes of the matching prefixes and re-checking for
+   * positive cache hits. See ephy_gsb_service_update_full_hashes_cb().
+   */
+  matching_prefixes = g_hash_table_get_keys (matching_prefixes_set);
+  ephy_gsb_service_update_full_hashes (self, threats,
+                                       matching_prefixes, matching_hashes,
+                                       callback, user_data);
+  goto out;
+
+return_result:
+  callback (threats, user_data);
+
+out:
+  g_list_free (matching_prefixes);
+  g_list_free (matching_hashes);
+  g_list_free_full (cues, (GDestroyNotify)g_bytes_unref);
+  g_list_free_full (prefixes_lookup, (GDestroyNotify)ephy_gsb_hash_prefix_lookup_free);
+  g_list_free_full (hashes_lookup, (GDestroyNotify)ephy_gsb_hash_full_lookup_free);
+  g_hash_table_unref (matching_prefixes_set);
+  g_hash_table_unref (matching_hashes_set);
+}
+
+void
+ephy_gsb_service_verify_url (EphyGSBService                  *self,
+                             const char                      *url,
+                             EphyGSBServiceVerifyURLCallback  callback,
+                             gpointer                         user_data)
+{
+  GHashTable *threats;
+  GList *hashes;
+
+  g_assert (EPHY_IS_GSB_SERVICE (self));
+  g_assert (url);
+
+  if (!callback)
+    return;
+
+  threats = g_hash_table_new_full (g_direct_hash,
+                                   (GEqualFunc)ephy_gsb_threat_list_equal,
+                                   (GDestroyNotify)ephy_gsb_threat_list_free,
+                                   NULL);
+
+  /* If the local database is broken or an update is in course, we cannot
+   * really verify the URL, so we have no choice other than to consider it safe.
+   */
+  if (!ephy_gsb_storage_is_operable (self->storage) || self->is_updating) {
+    LOG ("Local GSB storage is not available at the moment, cannot verify URL");
+    callback (threats, user_data);
+    return;
+  }
+
+  hashes = ephy_gsb_utils_compute_hashes (url);
+  ephy_gsb_service_verify_hashes (self, hashes, threats, callback, user_data);
+  g_list_free_full (hashes, (GDestroyNotify)g_bytes_unref);
+}
diff --git a/lib/safe-browsing/ephy-gsb-service.h b/lib/safe-browsing/ephy-gsb-service.h
index 8371394..d099c67 100644
--- a/lib/safe-browsing/ephy-gsb-service.h
+++ b/lib/safe-browsing/ephy-gsb-service.h
@@ -28,7 +28,17 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphyGSBService, ephy_gsb_service, EPHY, GSB_SERVICE, GObject)
 
-EphyGSBService *ephy_gsb_service_new (const char *api_key,
-                                      const char *db_path);
+/* @threats is a set of EphyGSBThreatList where the URL is considered unsafe.
+ * The caller takes ownership of the GHashTable and needs to free it.
+ */
+typedef void (*EphyGSBServiceVerifyURLCallback) (GHashTable *threats,
+                                                gpointer    user_data);
+
+EphyGSBService *ephy_gsb_service_new        (const char *api_key,
+                                             const char *db_path);
+void            ephy_gsb_service_verify_url (EphyGSBService                  *self,
+                                             const char                      *url,
+                                             EphyGSBServiceVerifyURLCallback  callback,
+                                             gpointer                         user_data);
 
 G_END_DECLS
diff --git a/lib/safe-browsing/ephy-gsb-storage.c b/lib/safe-browsing/ephy-gsb-storage.c
index 58b9677..e937703 100644
--- a/lib/safe-browsing/ephy-gsb-storage.c
+++ b/lib/safe-browsing/ephy-gsb-storage.c
@@ -28,8 +28,6 @@
 #include <glib/gstdio.h>
 #include <string.h>
 
-#define CUE_LEN 4
-
 /* Keep this lower than 200 or else you'll get "too many SQL variables" error
  * in ephy_gsb_storage_insert_batch(). SQLITE_MAX_VARIABLE_NUMBER is hardcoded
  * in sqlite3 as 999.
@@ -1064,7 +1062,7 @@ ephy_gsb_storage_insert_hash_prefix_batch (EphyGSBStorage      *self,
   }
 
   for (gsize k = start; k < end; k += len) {
-    if (!ephy_sqlite_statement_bind_blob (statement, id++, prefixes + k, CUE_LEN, NULL) ||
+    if (!ephy_sqlite_statement_bind_blob (statement, id++, prefixes + k, GSB_CUE_LEN, NULL) ||
         !ephy_sqlite_statement_bind_blob (statement, id++, prefixes + k, len, NULL) ||
         !bind_threat_list_params (statement, list, id, id + 1, id + 2, -1)) {
       g_warning ("Failed to bind values in hash prefix statement");
@@ -1167,7 +1165,7 @@ ephy_gsb_storage_lookup_hash_prefixes (EphyGSBStorage *self,
 
   for (GList *l = cues; l && l->data; l = l->next) {
     ephy_sqlite_statement_bind_blob (statement, id++,
-                                     g_bytes_get_data (l->data, NULL), CUE_LEN,
+                                     g_bytes_get_data (l->data, NULL), GSB_CUE_LEN,
                                      &error);
     if (error) {
       g_warning ("Failed to bind cue value as blob: %s", error->message);
diff --git a/lib/safe-browsing/ephy-gsb-utils.c b/lib/safe-browsing/ephy-gsb-utils.c
index db42623..4624b13 100644
--- a/lib/safe-browsing/ephy-gsb-utils.c
+++ b/lib/safe-browsing/ephy-gsb-utils.c
@@ -65,6 +65,23 @@ ephy_gsb_threat_list_free (EphyGSBThreatList *list)
   g_slice_free (EphyGSBThreatList, list);
 }
 
+gboolean
+ephy_gsb_threat_list_equal (EphyGSBThreatList *l1,
+                            EphyGSBThreatList *l2)
+{
+  g_assert (l1);
+  g_assert (l2);
+
+  if (g_strcmp0 (l1->threat_type, l2->threat_type) != 0)
+    return FALSE;
+  if (g_strcmp0 (l1->platform_type, l2->platform_type) != 0)
+    return FALSE;
+  if (g_strcmp0 (l1->threat_entry_type, l2->threat_entry_type) != 0)
+    return FALSE;
+
+  return TRUE;
+}
+
 EphyGSBHashPrefixLookup *
 ephy_gsb_hash_prefix_lookup_new (const guint8 *prefix,
                                  gsize         length,
@@ -631,3 +648,40 @@ ephy_gsb_utils_compute_hashes (const char *url)
 
   return g_list_reverse (retval);
 }
+
+GList *
+ephy_gsb_utils_get_hash_cues (GList *hashes)
+{
+  GList *retval = NULL;
+
+  g_assert (hashes);
+
+  for (GList *l = hashes; l && l->data; l = l->next) {
+    const char *hash = g_bytes_get_data (l->data, NULL);
+    retval = g_list_prepend (retval, g_bytes_new (hash, GSB_CUE_LEN));
+  }
+
+  return g_list_reverse (retval);
+}
+
+gboolean
+ephy_gsb_utils_hash_has_prefix (GBytes *hash,
+                                GBytes *prefix)
+{
+  const guint8 *hash_data;
+  const guint8 *prefix_data;
+  gsize prefix_len;
+
+  g_assert (hash);
+  g_assert (prefix);
+
+  hash_data = g_bytes_get_data (hash, NULL);
+  prefix_data = g_bytes_get_data (prefix, &prefix_len);
+
+  for (gsize i = 0; i < prefix_len; i++) {
+    if (hash_data[i] != prefix_data[i])
+      return FALSE;
+  }
+
+  return TRUE;
+}
diff --git a/lib/safe-browsing/ephy-gsb-utils.h b/lib/safe-browsing/ephy-gsb-utils.h
index 759f525..81c06c3 100644
--- a/lib/safe-browsing/ephy-gsb-utils.h
+++ b/lib/safe-browsing/ephy-gsb-utils.h
@@ -25,6 +25,7 @@
 
 G_BEGIN_DECLS
 
+#define GSB_CUE_LEN   4
 #define GSB_HASH_TYPE G_CHECKSUM_SHA256
 #define GSB_HASH_SIZE (g_checksum_type_get_length (GSB_HASH_TYPE))
 
@@ -60,6 +61,8 @@ EphyGSBThreatList       *ephy_gsb_threat_list_new                 (const char *t
                                                                    const char *client_state,
                                                                    gint64      timestamp);
 void                     ephy_gsb_threat_list_free                (EphyGSBThreatList *list);
+gboolean                 ephy_gsb_threat_list_equal               (EphyGSBThreatList *l1,
+                                                                   EphyGSBThreatList *l2);
 
 EphyGSBHashPrefixLookup *ephy_gsb_hash_prefix_lookup_new          (const guint8 *prefix,
                                                                    gsize         length,
@@ -87,5 +90,8 @@ char                    *ephy_gsb_utils_canonicalize              (const char  *
                                                                    char       **path_out,
                                                                    char       **query_out);
 GList                   *ephy_gsb_utils_compute_hashes            (const char *url);
+GList                   *ephy_gsb_utils_get_hash_cues             (GList *hashes);
+gboolean                 ephy_gsb_utils_hash_has_prefix           (GBytes *hash,
+                                                                   GBytes *prefix);
 
 G_END_DECLS


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]