[epiphany/wip/google-safe-browsing] safe-browsing: Implement database update operation



commit 9be0c3725d677e7b9869728d1ceffc96366c85f7
Author: Gabriel Ivascu <gabrielivascu gnome org>
Date:   Tue Sep 12 20:19:25 2017 +0300

    safe-browsing: Implement database update operation
    
    The initial update is too large and it takes a few seconds to complete.
    Will have to change this to run in a separate thread to not block the UI thread.

 data/org.gnome.epiphany.gschema.xml  |   10 +
 embed/ephy-embed-shell.c             |    5 +-
 lib/ephy-prefs.h                     |    2 +
 lib/meson.build                      |    2 +
 lib/safe-browsing/ephy-gsb-service.c |  224 ++++++++++++-
 lib/safe-browsing/ephy-gsb-service.h |    3 +-
 lib/safe-browsing/ephy-gsb-storage.c |  648 ++++++++++++++++++++++++++++++++-
 lib/safe-browsing/ephy-gsb-storage.h |   24 ++-
 lib/safe-browsing/ephy-gsb-utils.c   |  108 ++++++
 lib/safe-browsing/ephy-gsb-utils.h   |   46 +++
 10 files changed, 1045 insertions(+), 27 deletions(-)
---
diff --git a/data/org.gnome.epiphany.gschema.xml b/data/org.gnome.epiphany.gschema.xml
index fa09bfc..4407f7a 100644
--- a/data/org.gnome.epiphany.gschema.xml
+++ b/data/org.gnome.epiphany.gschema.xml
@@ -222,6 +222,16 @@
                        <summary>Enable site-specific quirks</summary>
                        <description>Enable quirks to make specific websites work better. You might want to 
disable this setting if debugging a specific issue.</description>
                </key>
+               <key type="b" name="enable-safe-browsing">
+                       <default>true</default>
+                       <summary>Enable safe browsing</summary>
+                       <description>Whether to enable safe browsing. Safe browsing operates via Google Safe 
Browsing API v4.</description>
+               </key>
+               <key type="s" name="gsb-api-key">
+                       <default>'AIzaSyAtuURrRblYXvwCyDC5ZFq0mEw1x4VN6KA'</default>
+                       <summary>Google Safe Browsing API key</summary>
+                       <description>The API key used to access the Google Safe Browsing API v4.</description>
+               </key>
        </schema>
        <schema id="org.gnome.Epiphany.state">
                <key type="s" name="download-dir">
diff --git a/embed/ephy-embed-shell.c b/embed/ephy-embed-shell.c
index c43b641..242380a 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -590,11 +590,14 @@ ephy_embed_shell_get_safe_browsing_service (EphyEmbedShell *shell)
   g_return_val_if_fail (EPHY_IS_EMBED_SHELL (shell), NULL);
 
   if (priv->safe_browsing_service == NULL) {
+    char *api_key;
     char *filename;
 
+    api_key = g_settings_get_string (EPHY_SETTINGS_WEB, EPHY_PREFS_WEB_GSB_API_KEY);
     filename = g_build_filename (ephy_dot_dir (), EPHY_GSB_FILE, NULL);
-    priv->safe_browsing_service = ephy_gsb_service_new (filename);
+    priv->safe_browsing_service = ephy_gsb_service_new (api_key, filename);
 
+    g_free (api_key);
     g_free (filename);
   }
 
diff --git a/lib/ephy-prefs.h b/lib/ephy-prefs.h
index 9fafee4..17b80c7 100644
--- a/lib/ephy-prefs.h
+++ b/lib/ephy-prefs.h
@@ -99,6 +99,8 @@ static const char * const ephy_prefs_state_schema[] = {
 #define EPHY_PREFS_WEB_ENABLE_ADBLOCK              "enable-adblock"
 #define EPHY_PREFS_WEB_REMEMBER_PASSWORDS          "remember-passwords"
 #define EPHY_PREFS_WEB_ENABLE_SITE_SPECIFIC_QUIRKS "enable-site-specific-quirks"
+#define EPHY_PREFS_WEB_ENABLE_SAFE_BROWSING        "enable-safe-browsing"
+#define EPHY_PREFS_WEB_GSB_API_KEY                 "gsb-api-key"
 
 static const char * const ephy_prefs_web_schema[] = {
   EPHY_PREFS_WEB_FONT_MIN_SIZE,
diff --git a/lib/meson.build b/lib/meson.build
index 48e5d15..9a52a7e 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -49,6 +49,7 @@ libephymisc_sources = [
   'history/ephy-history-types.c',
   'safe-browsing/ephy-gsb-service.c',
   'safe-browsing/ephy-gsb-storage.c',
+  'safe-browsing/ephy-gsb-utils.c',
   enums
 ]
 
@@ -61,6 +62,7 @@ libephymisc_deps = [
   gnome_desktop_dep,
   gtk_dep,
   icu_uc_dep,
+  json_glib_dep,
   libsecret_dep,
   libsoup_dep,
   libxml_dep,
diff --git a/lib/safe-browsing/ephy-gsb-service.c b/lib/safe-browsing/ephy-gsb-service.c
index dda2add..04e6fcb 100644
--- a/lib/safe-browsing/ephy-gsb-service.c
+++ b/lib/safe-browsing/ephy-gsb-service.c
@@ -23,23 +23,201 @@
 
 #include "ephy-debug.h"
 #include "ephy-gsb-storage.h"
+#include "ephy-user-agent.h"
+
+#include <libsoup/soup.h>
+#include <math.h>
+#include <stdio.h>
+#include <string.h>
+
+#define API_PREFIX        "https://safebrowsing.googleapis.com/v4/";
+#define CURRENT_TIME      (g_get_real_time () / 1000000)  /* seconds */
+#define DEFAULT_WAIT_TIME (30 * 60)                       /* seconds */
 
 struct _EphyGSBService {
   GObject parent_instance;
 
+  char           *api_key;
   EphyGSBStorage *storage;
+  SoupSession    *session;
 };
 
 G_DEFINE_TYPE (EphyGSBService, ephy_gsb_service, G_TYPE_OBJECT);
 
 enum {
   PROP_0,
+  PROP_API_KEY,
   PROP_GSB_STORAGE,
   LAST_PROP
 };
 
 static GParamSpec *obj_properties[LAST_PROP];
 
+static inline gboolean
+json_object_has_non_null_string_member (JsonObject *object,
+                                        const char *member)
+{
+  return json_object_has_member (object, member) &&
+         json_object_get_string_member (object, member) != NULL;
+}
+
+static inline gboolean
+json_object_has_non_null_array_member (JsonObject *object,
+                                       const char *member)
+{
+  return json_object_has_member (object, member) &&
+         json_object_get_array_member (object, member) != NULL;
+}
+
+static void
+update_threat_lists_cb (SoupSession *session,
+                        SoupMessage *msg,
+                        gpointer     user_data)
+{
+  EphyGSBService *self = EPHY_GSB_SERVICE (user_data);
+  GError *error = NULL;
+  JsonNode *node;
+  JsonObject *object;
+  JsonArray *responses;
+  gint64 next_update_time;
+
+  if (msg->status_code != 200) {
+    LOG ("Cannot update GSB threat lists. Server responded: %u, %s",
+         msg->status_code, msg->response_body->data);
+    return;
+  }
+
+  node = json_from_string (msg->response_body->data, &error);
+  if (error) {
+    g_warning ("Cannot update GSB threat lists. Response is not a valid JSON: %s", error->message);
+    g_error_free (error);
+    return;
+  }
+
+  object = json_node_get_object (node);
+  responses = json_object_get_array_member (object, "listUpdateResponses");
+
+  for (guint i = 0; i < json_array_get_length (responses); i++) {
+    EphyGSBThreatList *list;
+    JsonObject *lur = json_array_get_object_element (responses, i);
+    const char *type = json_object_get_string_member (lur, "responseType");
+    JsonObject *checksum = json_object_get_object_member (lur, "checksum");
+    const char *remote_checksum = json_object_get_string_member (checksum, "sha256");
+    char *local_checksum;
+
+    list = ephy_gsb_threat_list_new (json_object_get_string_member (lur, "threatType"),
+                                     json_object_get_string_member (lur, "platformType"),
+                                     json_object_get_string_member (lur, "threatEntryType"),
+                                     json_object_get_string_member (lur, "newClientState"),
+                                     CURRENT_TIME);
+    LOG ("Updating list %s/%s/%s", list->threat_type, list->platform_type, list->threat_entry_type);
+
+    /* If full update, clear all previous hash prefixes for the given list. */
+    if (!g_strcmp0 (type, "FULL_UPDATE")) {
+      LOG ("FULL UPDATE, clearing all previous hash prefixes...");
+      ephy_gsb_storage_clear_hash_prefixes (self->storage, list);
+    }
+
+    /* Removals need to be handled before additions. */
+    if (json_object_has_non_null_array_member (lur, "removals")) {
+      JsonArray *removals = json_object_get_array_member (lur, "removals");
+      for (guint k = 0; k < json_array_get_length (removals); k++) {
+        JsonObject *tes = json_array_get_object_element (removals, k);
+        JsonObject *raw_indices = json_object_get_object_member (tes, "rawIndices");
+        JsonArray *indices = json_object_get_array_member (raw_indices, "indices");
+        ephy_gsb_storage_delete_hash_prefixes (self->storage, list, indices);
+      }
+    }
+
+    /* Handle additions. */
+    if (json_object_has_non_null_array_member (lur, "additions")) {
+      JsonArray *additions = json_object_get_array_member (lur, "additions");
+      for (guint k = 0; k < json_array_get_length (additions); k++) {
+        JsonObject *tes = json_array_get_object_element (additions, k);
+        JsonObject *raw_hashes = json_object_get_object_member (tes, "rawHashes");
+        gint64 prefix_size = json_object_get_int_member (raw_hashes, "prefixSize");
+        const char *hashes = json_object_get_string_member (raw_hashes, "rawHashes");
+        ephy_gsb_storage_insert_hash_prefixes (self->storage, list, prefix_size, hashes);
+      }
+    }
+
+    /* Verify checksum. */
+    local_checksum = ephy_gsb_storage_compute_checksum (self->storage, list);
+    if (!g_strcmp0 (local_checksum, remote_checksum)) {
+      LOG ("Local checksum matches the remote checksum, updating client states...");
+      ephy_gsb_storage_update_client_state (self->storage, list, FALSE);
+    } else {
+      LOG ("Local checksum does NOT match the remote checksum, clearing list...");
+      ephy_gsb_storage_clear_hash_prefixes (self->storage, list);
+      ephy_gsb_storage_update_client_state (self->storage, list, TRUE);
+    }
+
+    g_free (local_checksum);
+    ephy_gsb_threat_list_free (list);
+  }
+
+  /* Update next update time. */
+  if (json_object_has_non_null_string_member (object, "minimumWaitDuration")) {
+    const char *duration_str;
+    double duration;
+
+    duration_str = json_object_get_string_member (object, "minimumWaitDuration");
+    /* Handle the trailing 's' character. */
+    sscanf (duration_str, "%lfs", &duration);
+    next_update_time = CURRENT_TIME + (gint64)ceil (duration);
+  } else {
+    next_update_time = CURRENT_TIME + DEFAULT_WAIT_TIME;
+  }
+
+  ephy_gsb_storage_set_next_update_time (self->storage, next_update_time);
+  /* TODO: Schedule a next update in (next_update_time - CURRENT_TIME) seconds. */
+
+  json_node_unref (node);
+}
+
+static void
+ephy_gsb_service_update_threat_lists (EphyGSBService *self)
+{
+  SoupMessage *msg;
+  JsonNode *body_node;
+  JsonObject *body_obj;
+  JsonArray *requests;
+  GList *threat_lists;
+  char *url;
+  char *body;
+
+  g_assert (EPHY_IS_GSB_SERVICE (self));
+  g_assert (ephy_gsb_storage_is_operable (self->storage));
+
+  threat_lists = ephy_gsb_storage_get_threat_lists (self->storage);
+  if (!threat_lists)
+    return;
+
+  requests = json_array_new ();
+  for (GList *l = threat_lists; l && l->data; l = l->next) {
+    JsonObject *lur = ephy_gsb_utils_make_list_update_request (l->data);
+    json_array_add_object_element (requests, lur);
+  }
+
+  body_obj = json_object_new ();
+  json_object_set_object_member (body_obj, "client", ephy_gsb_utils_make_client_info ());
+  json_object_set_array_member (body_obj, "listUpdateRequests", requests);
+
+  body_node = json_node_new (JSON_NODE_OBJECT);
+  json_node_set_object (body_node, body_obj);
+  body = json_to_string (body_node, FALSE);
+
+  url = g_strdup_printf ("%sthreatListUpdates:fetch?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, update_threat_lists_cb, self);
+
+  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,
@@ -49,6 +227,10 @@ ephy_gsb_service_set_property (GObject      *object,
   EphyGSBService *self = EPHY_GSB_SERVICE (object);
 
   switch (prop_id) {
+    case PROP_API_KEY:
+      g_free (self->api_key);
+      self->api_key = g_strdup (g_value_get_string (value));
+      break;
     case PROP_GSB_STORAGE:
       if (self->storage)
         g_object_unref (self->storage);
@@ -68,6 +250,9 @@ ephy_gsb_service_get_property (GObject    *object,
   EphyGSBService *self = EPHY_GSB_SERVICE (object);
 
   switch (prop_id) {
+    case PROP_API_KEY:
+      g_value_set_string (value, self->api_key);
+      break;
     case PROP_GSB_STORAGE:
       g_value_set_object (value, self->storage);
       break;
@@ -77,11 +262,22 @@ ephy_gsb_service_get_property (GObject    *object,
 }
 
 static void
+ephy_gsb_service_finalize (GObject *object)
+{
+  EphyGSBService *self = EPHY_GSB_SERVICE (object);
+
+  g_free (self->api_key);
+
+  G_OBJECT_CLASS (ephy_gsb_service_parent_class)->finalize (object);
+}
+
+static void
 ephy_gsb_service_dispose (GObject *object)
 {
   EphyGSBService *self = EPHY_GSB_SERVICE (object);
 
   g_clear_object (&self->storage);
+  g_clear_object (&self->session);
 
   G_OBJECT_CLASS (ephy_gsb_service_parent_class)->dispose (object);
 }
@@ -90,15 +286,25 @@ static void
 ephy_gsb_service_constructed (GObject *object)
 {
   EphyGSBService *self = EPHY_GSB_SERVICE (object);
+  gint64 next_update_time;
 
   G_OBJECT_CLASS (ephy_gsb_service_parent_class)->constructed (object);
 
-  /* TODO: Perform an initial database update if necessary. */
+  if (!ephy_gsb_storage_is_operable (self->storage))
+    return;
+
+  next_update_time = ephy_gsb_storage_get_next_update_time (self->storage);
+  if (CURRENT_TIME >= next_update_time) {
+    /* TODO: This takes too long, needs to run in a separate thread. */
+    ephy_gsb_service_update_threat_lists (self);
+  }
 }
 
 static void
 ephy_gsb_service_init (EphyGSBService *self)
 {
+  self->session = soup_session_new ();
+  g_object_set (self->session, "user-agent", ephy_user_agent_get_internal (), NULL);
 }
 
 static void
@@ -110,6 +316,14 @@ ephy_gsb_service_class_init (EphyGSBServiceClass *klass)
   object_class->get_property = ephy_gsb_service_get_property;
   object_class->constructed = ephy_gsb_service_constructed;
   object_class->dispose = ephy_gsb_service_dispose;
+  object_class->finalize = ephy_gsb_service_finalize;
+
+  obj_properties[PROP_API_KEY] =
+    g_param_spec_string ("api-key",
+                         "API key",
+                         "The API key to access the Google Safe Browsing API",
+                         NULL,
+                         G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);
 
   obj_properties[PROP_GSB_STORAGE] =
     g_param_spec_object ("gsb-storage",
@@ -122,13 +336,17 @@ ephy_gsb_service_class_init (EphyGSBServiceClass *klass)
 }
 
 EphyGSBService *
-ephy_gsb_service_new (const char *db_path)
+ephy_gsb_service_new (const char *api_key,
+                      const char *db_path)
 {
   EphyGSBService *service;
   EphyGSBStorage *storage;
 
   storage = ephy_gsb_storage_new (db_path);
-  service = g_object_new (EPHY_TYPE_GSB_SERVICE, "gsb-storage", storage, NULL);
+  service = g_object_new (EPHY_TYPE_GSB_SERVICE,
+                          "api-key", api_key,
+                          "gsb-storage", storage,
+                          NULL);
   g_object_unref (storage);
 
   return service;
diff --git a/lib/safe-browsing/ephy-gsb-service.h b/lib/safe-browsing/ephy-gsb-service.h
index bcb4072..8371394 100644
--- a/lib/safe-browsing/ephy-gsb-service.h
+++ b/lib/safe-browsing/ephy-gsb-service.h
@@ -28,6 +28,7 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphyGSBService, ephy_gsb_service, EPHY, GSB_SERVICE, GObject)
 
-EphyGSBService *ephy_gsb_service_new (const char *db_path);
+EphyGSBService *ephy_gsb_service_new (const char *api_key,
+                                      const char *db_path);
 
 G_END_DECLS
diff --git a/lib/safe-browsing/ephy-gsb-storage.c b/lib/safe-browsing/ephy-gsb-storage.c
index 4168268..275a6e7 100644
--- a/lib/safe-browsing/ephy-gsb-storage.c
+++ b/lib/safe-browsing/ephy-gsb-storage.c
@@ -26,10 +26,32 @@
 
 #include <errno.h>
 #include <glib/gstdio.h>
+#include <string.h>
 
-/* Update this if you modify the database table structure. */
+#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.
+ */
+#define BATCH_SIZE 199
+
+/* Update schema version if you:
+ * 1) Modify the database table structure.
+ * 2) Add new threat lists below.
+ */
 #define SCHEMA_VERSION "1.0"
 
+/* The available Linux threat lists of Google Safe Browsing API v4.
+ * The format is {THREAT_TYPE, PLATFORM_TYPE, THREAT_ENTRY_TYPE}.
+ */
+static const char * const gsb_linux_threat_lists[][3] = {
+  {"MALWARE",            "LINUX", "URL"},
+  {"SOCIAL_ENGINEERING", "LINUX", "URL"},
+  {"UNWANTED_SOFTWARE",  "LINUX", "URL"},
+  {"MALWARE",            "LINUX", "IP_RANGE"},
+};
+
 struct _EphyGSBStorage {
   GObject parent_instance;
 
@@ -50,6 +72,55 @@ enum {
 static GParamSpec *obj_properties[LAST_PROP];
 
 static gboolean
+bind_threat_list_params (EphySQLiteStatement *statement,
+                         EphyGSBThreatList   *list,
+                         int                  threat_type_col,
+                         int                  platform_type_col,
+                         int                  threat_entry_type_col,
+                         int                  client_state_col)
+{
+  GError *error = NULL;
+
+  g_assert (statement);
+  g_assert (list);
+
+  if (list->threat_type && threat_type_col >= 0) {
+    ephy_sqlite_statement_bind_string (statement, threat_type_col, list->threat_type, &error);
+    if (error) {
+      g_warning ("Failed to bind threat type: %s", error->message);
+      g_error_free (error);
+      return FALSE;
+    }
+  }
+  if (list->platform_type && platform_type_col >= 0) {
+    ephy_sqlite_statement_bind_string (statement, platform_type_col, list->platform_type, &error);
+    if (error) {
+      g_warning ("Failed to bind platform type: %s", error->message);
+      g_error_free (error);
+      return FALSE;
+    }
+  }
+  if (list->threat_entry_type && threat_entry_type_col >= 0) {
+    ephy_sqlite_statement_bind_string (statement, threat_entry_type_col, list->threat_entry_type, &error);
+    if (error) {
+      g_warning ("Failed to bind threat entry type: %s", error->message);
+      g_error_free (error);
+      return FALSE;
+    }
+  }
+  if (list->client_state && client_state_col >= 0) {
+    ephy_sqlite_statement_bind_string (statement, client_state_col, list->client_state, &error);
+    if (error) {
+      g_warning ("Failed to bind client state: %s", error->message);
+      g_error_free (error);
+      return FALSE;
+    }
+  }
+
+  return TRUE;
+}
+
+static gboolean
 ephy_gsb_storage_init_metadata_table (EphyGSBStorage *self)
 {
   GError *error = NULL;
@@ -62,8 +133,8 @@ ephy_gsb_storage_init_metadata_table (EphyGSBStorage *self)
     return TRUE;
 
   sql = "CREATE TABLE metadata ("
-        "name LONGVARCHAR NOT NULL PRIMARY KEY,"
-        "value LONGVARCHAR NOT NULL"
+        "name VARCHAR NOT NULL PRIMARY KEY,"
+        "value VARCHAR NOT NULL"
         ")";
   ephy_sqlite_connection_execute (self->db, sql, &error);
   if (error) {
@@ -88,8 +159,12 @@ ephy_gsb_storage_init_metadata_table (EphyGSBStorage *self)
 static gboolean
 ephy_gsb_storage_init_threats_table (EphyGSBStorage *self)
 {
+  EphySQLiteStatement *statement = NULL;
   GError *error = NULL;
+  GString *string = NULL;
   const char *sql;
+  int num_lists;
+  gboolean retval = FALSE;
 
   g_assert (EPHY_IS_GSB_STORAGE (self));
   g_assert (EPHY_IS_SQLITE_CONNECTION (self->db));
@@ -98,21 +173,59 @@ ephy_gsb_storage_init_threats_table (EphyGSBStorage *self)
     return TRUE;
 
   sql = "CREATE TABLE threats ("
-        "threat_type LONGVARCHAR NOT NULL,"
-        "platform_type LONGVARCHAR NOT NULL,"
-        "threat_entry_type LONGVARCHAR NOT NULL,"
-        "client_state LONGVARCHAR,"
+        "threat_type VARCHAR NOT NULL,"
+        "platform_type VARCHAR NOT NULL,"
+        "threat_entry_type VARCHAR NOT NULL,"
+        "client_state VARCHAR,"
         "timestamp INTEGER NOT NULL DEFAULT (CAST(strftime('%s', 'now') AS INT)),"
         "PRIMARY KEY (threat_type, platform_type, threat_entry_type)"
         ")";
   ephy_sqlite_connection_execute (self->db, sql, &error);
   if (error) {
     g_warning ("Failed to create threats table: %s", error->message);
-    g_error_free (error);
-    return FALSE;
+    goto out;
   }
 
-  return TRUE;
+  num_lists = sizeof (gsb_linux_threat_lists) / sizeof (gsb_linux_threat_lists[0]);
+  sql = "INSERT INTO threats (threat_type, platform_type, threat_entry_type) VALUES ";
+  string = g_string_new (sql);
+  for (int i = 0; i < num_lists; i++)
+    g_string_append (string, "(?, ?, ?),");
+  /* Remove trailing comma character. */
+  g_string_erase (string, string->len - 1, -1);
+
+  statement = ephy_sqlite_connection_create_statement (self->db, string->str, &error);
+  if (error) {
+    g_warning ("Failed to create threats table insert statement: %s", error->message);
+    goto out;
+  }
+
+  for (int i = 0; i < num_lists; i++) {
+    EphyGSBThreatList *list = ephy_gsb_threat_list_new (gsb_linux_threat_lists[i][0],
+                                                        gsb_linux_threat_lists[i][1],
+                                                        gsb_linux_threat_lists[i][2],
+                                                        NULL, 0);
+    bind_threat_list_params (statement, list, i * 3, i * 3 + 1, i * 3 + 2, -1);
+    ephy_gsb_threat_list_free (list);
+  }
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error) {
+    g_warning ("Failed to insert initial data into threats table: %s", error->message);
+    goto out;
+  }
+
+  retval = TRUE;
+
+out:
+  if (string)
+    g_string_free (string, TRUE);
+  if (statement)
+    g_object_unref (statement);
+  if (error)
+    g_error_free (error);
+
+  return retval;
 }
 
 static gboolean
@@ -129,10 +242,10 @@ ephy_gsb_storage_init_hash_prefix_table (EphyGSBStorage *self)
 
   sql = "CREATE TABLE hash_prefix ("
         "cue BLOB NOT NULL,"    /* The first 4 bytes. */
-        "value BLOB NOT NULL,"  /* The prefix itself, can vary from 4 bytes to 32 bytes. */
-        "threat_type LONGVARCHAR NOT NULL,"
-        "platform_type LONGVARCHAR NOT NULL,"
-        "threat_entry_type LONGVARCHAR NOT NULL,"
+        "value BLOB NOT NULL,"  /* The prefix itself, can vary from 4 to 32 bytes. */
+        "threat_type VARCHAR NOT NULL,"
+        "platform_type VARCHAR NOT NULL,"
+        "threat_entry_type VARCHAR NOT NULL,"
         "timestamp INTEGER NOT NULL DEFAULT (CAST(strftime('%s', 'now') AS INT)),"
         "negative_expires_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s', 'now') AS INT)),"
         "PRIMARY KEY (value, threat_type, platform_type, threat_entry_type),"
@@ -164,12 +277,12 @@ ephy_gsb_storage_init_hash_full_table (EphyGSBStorage *self)
 
   sql = "CREATE TABLE hash_full ("
         "value BLOB NOT NULL,"  /* The 32 bytes full hash. */
-        "threat_type LONGVARCHAR NOT NULL,"
-        "platform_type LONGVARCHAR NOT NULL,"
-        "threat_entry_type LONGVARCHAR NOT NULL,"
+        "threat_type VARCHAR NOT NULL,"
+        "platform_type VARCHAR NOT NULL,"
+        "threat_entry_type VARCHAR NOT NULL,"
         "timestamp INTEGER NOT NULL DEFAULT (CAST(strftime('%s', 'now') AS INT)),"
         "expires_at INTEGER NOT NULL DEFAULT (CAST(strftime('%s', 'now') AS INT)),"
-        "malware_threat_type LONGVARCHAR," /* Not all threats have this field set. */
+        "malware_threat_type VARCHAR," /* Not all threats have this field set. */
         "PRIMARY KEY (value, threat_type, platform_type, threat_entry_type)"
         ")";
   ephy_sqlite_connection_execute (self->db, sql, &error);
@@ -208,12 +321,18 @@ ephy_gsb_storage_open_db (EphyGSBStorage *self)
   }
 
   /* Enable foreign keys. */
-  ephy_sqlite_connection_execute (self->db, "PRAGMA foreign_keys = ON", &error);
+  ephy_sqlite_connection_execute (self->db, "PRAGMA foreign_keys=ON", &error);
   if (error) {
     g_warning ("Failed to enable foreign keys pragma: %s", error->message);
     goto out_err;
   }
 
+  ephy_sqlite_connection_execute (self->db, "PRAGMA synchronous=OFF", &error);
+  if (error) {
+    g_warning ("Failed to disable synchronous pragma: %s", error->message);
+    goto out_err;
+  }
+
   return TRUE;
 
 out_err:
@@ -278,7 +397,7 @@ ephy_gsb_storage_check_schema_version (EphyGSBStorage *self)
   sql = "SELECT value FROM metadata WHERE name='schema_version'";
   statement = ephy_sqlite_connection_create_statement (self->db, sql, &error);
   if (error) {
-    g_warning ("Failed to build select schema version statement: %s", error->message);
+    g_warning ("Failed to create select schema version statement: %s", error->message);
     g_error_free (error);
     return FALSE;
   }
@@ -416,3 +535,492 @@ ephy_gsb_storage_is_operable (EphyGSBStorage *self)
 
   return self->is_operable;
 }
+
+gint64
+ephy_gsb_storage_get_next_update_time (EphyGSBStorage *self)
+{
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  const char *next_update_at;
+  const char *sql;
+  gint64 next_update_time;
+
+  g_return_val_if_fail (EPHY_IS_GSB_STORAGE (self), G_MAXINT64);
+  g_return_val_if_fail (self->is_operable, G_MAXINT64);
+
+  sql = "SELECT value FROM metadata WHERE name='next_update_at'";
+  statement = ephy_sqlite_connection_create_statement (self->db, sql, &error);
+  if (error) {
+    g_warning ("Failed to create select next update statement: %s", error->message);
+    g_error_free (error);
+    return G_MAXINT64;
+  }
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error) {
+    g_warning ("Failed to retrieve next update time: %s", error->message);
+    g_error_free (error);
+    g_object_unref (statement);
+    return G_MAXINT64;
+  }
+
+  next_update_at = ephy_sqlite_statement_get_column_as_string (statement, 0);
+  sscanf (next_update_at, "%ld", &next_update_time);
+
+  g_object_unref (statement);
+
+  return next_update_time;
+}
+
+void
+ephy_gsb_storage_set_next_update_time (EphyGSBStorage *self,
+                                       gint64          next_update_time)
+{
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  char *value = NULL;
+  const char *sql;
+
+  g_return_if_fail (EPHY_IS_GSB_STORAGE (self));
+  g_return_if_fail (self->is_operable);
+
+  sql = "UPDATE metadata SET value=? WHERE name='next_update_at'";
+  statement = ephy_sqlite_connection_create_statement (self->db, sql, &error);
+  if (error) {
+    g_warning ("Failed to create update next update time statement: %s", error->message);
+    goto out;
+  }
+
+  value = g_strdup_printf ("%ld", next_update_time);;
+  ephy_sqlite_statement_bind_string (statement, 0, value, &error);
+  if (error) {
+    g_warning ("Failed to bind string in next update time statement: %s", error->message);
+    goto out;
+  }
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error)
+    g_warning ("Failed to update next update time: %s", error->message);
+
+out:
+  g_free (value);
+  if (statement)
+    g_object_unref (statement);
+  if (error)
+    g_error_free (error);
+}
+
+GList *
+ephy_gsb_storage_get_threat_lists (EphyGSBStorage *self)
+{
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  GList *threat_lists = NULL;
+  const char *sql;
+
+  g_return_val_if_fail (EPHY_IS_GSB_STORAGE (self), NULL);
+  g_return_val_if_fail (self->is_operable, NULL);
+
+  sql = "SELECT threat_type, platform_type, threat_entry_type, client_state, timestamp FROM threats";
+  statement = ephy_sqlite_connection_create_statement (self->db, sql, &error);
+  if (error) {
+    g_warning ("Failed to create select threat lists statement: %s", error->message);
+    g_error_free (error);
+    return NULL;
+  }
+
+  while (ephy_sqlite_statement_step (statement, &error)) {
+    const char *threat_type = ephy_sqlite_statement_get_column_as_string (statement, 0);
+    const char *platform_type = ephy_sqlite_statement_get_column_as_string (statement, 1);
+    const char *threat_entry_type = ephy_sqlite_statement_get_column_as_string (statement, 2);
+    const char *client_state = ephy_sqlite_statement_get_column_as_string (statement, 3);
+    gint64 timestamp = ephy_sqlite_statement_get_column_as_int64 (statement, 4);
+    EphyGSBThreatList *list = ephy_gsb_threat_list_new (threat_type, platform_type,
+                                                        threat_entry_type, client_state,
+                                                        timestamp);
+    threat_lists = g_list_prepend (threat_lists, list);
+  }
+
+  if (error) {
+    g_warning ("Failed to execute select threat lists statement: %s", error->message);
+    g_error_free (error);
+  }
+
+  g_object_unref (statement);
+
+  return threat_lists;
+}
+
+char *
+ephy_gsb_storage_compute_checksum (EphyGSBStorage    *self,
+                                   EphyGSBThreatList *list)
+{
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  const char *sql;
+  char *retval = NULL;
+  GChecksum *checksum = NULL;
+  guint8 *digest = NULL;
+  gsize digest_len = g_checksum_type_get_length (G_CHECKSUM_SHA256);
+
+  g_return_val_if_fail (EPHY_IS_GSB_STORAGE (self), NULL);
+  g_return_val_if_fail (self->is_operable, NULL);
+  g_return_val_if_fail (list, NULL);
+
+  sql = "SELECT value FROM hash_prefix WHERE "
+        "threat_type=? AND platform_type=? AND threat_entry_type=? "
+        "ORDER BY value";
+  statement = ephy_sqlite_connection_create_statement (self->db, sql, &error);
+  if (error) {
+    g_warning ("Failed to create select hash prefix statement: %s", error->message);
+    goto out;
+  }
+
+  if (!bind_threat_list_params (statement, list, 0, 1, 2, -1))
+    goto out;
+
+  checksum = g_checksum_new (G_CHECKSUM_SHA256);
+  while (ephy_sqlite_statement_step (statement, &error)) {
+    g_checksum_update (checksum,
+                       ephy_sqlite_statement_get_column_as_blob (statement, 0),
+                       ephy_sqlite_statement_get_column_size (statement, 0));
+  }
+
+  if (error) {
+    g_warning ("Failed to execute select hash prefix statement: %s", error->message);
+    goto out;
+  }
+
+  digest = g_malloc (digest_len);
+  g_checksum_get_digest (checksum, digest, &digest_len);
+  retval = g_base64_encode (digest, digest_len);
+
+out:
+  g_free (digest);
+  if (statement)
+    g_object_unref (statement);
+  if (checksum)
+    g_checksum_free (checksum);
+  if (error)
+    g_error_free (error);
+
+  return retval;
+}
+
+void
+ephy_gsb_storage_update_client_state (EphyGSBStorage    *self,
+                                      EphyGSBThreatList *list,
+                                      gboolean           clear)
+{
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  const char *sql;
+
+  g_return_if_fail (EPHY_IS_GSB_STORAGE (self));
+  g_return_if_fail (self->is_operable);
+  g_return_if_fail (list);
+
+  if (clear) {
+    sql = "UPDATE threats SET "
+          "timestamp=(CAST(strftime('%s', 'now') AS INT)), client_state=NULL "
+          "WHERE threat_type=? AND platform_type=? AND threat_entry_type=?";
+  } else {
+    sql = "UPDATE threats SET "
+          "timestamp=(CAST(strftime('%s', 'now') AS INT)), client_state=? "
+          "WHERE threat_type=? AND platform_type=? AND threat_entry_type=?";
+  }
+
+  statement = ephy_sqlite_connection_create_statement (self->db, sql, &error);
+  if (error) {
+    g_warning ("Failed to create update threats statement: %s", error->message);
+    goto out;
+  }
+
+  if (!bind_threat_list_params (statement, list, 1, 2, 3, clear ? -1 : 0))
+    goto out;
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error)
+    g_warning ("Failed to execute update threat statement: %s", error->message);
+
+out:
+  if (statement)
+    g_object_unref (statement);
+  if (error)
+    g_error_free (error);
+}
+
+void
+ephy_gsb_storage_clear_hash_prefixes (EphyGSBStorage    *self,
+                                      EphyGSBThreatList *list)
+{
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  const char *sql;
+
+  g_return_if_fail (EPHY_IS_GSB_STORAGE (self));
+  g_return_if_fail (self->is_operable);
+  g_return_if_fail (list);
+
+  sql = "DELETE FROM hash_prefix WHERE "
+        "threat_type=? AND platform_type=? AND threat_entry_type=?";
+  statement = ephy_sqlite_connection_create_statement (self->db, sql, &error);
+  if (error) {
+    g_warning ("Failed to create delete hash prefix statement: %s", error->message);
+    goto out;
+  }
+
+  if (!bind_threat_list_params (statement, list, 0, 1, 2, -1))
+    goto out;
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error)
+    g_warning ("Failed to execute clear hash prefix statement: %s", error->message);
+
+out:
+  if (statement)
+    g_object_unref (statement);
+  if (error)
+    g_error_free (error);
+}
+
+static GList *
+ephy_gsb_storage_get_hash_prefixes_to_delete (EphyGSBStorage    *self,
+                                              EphyGSBThreatList *list,
+                                              GHashTable        *indices,
+                                              gsize             *num_prefixes)
+{
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  GList *prefixes = NULL;
+  const char *sql;
+  int index = 0;
+
+  g_assert (EPHY_IS_GSB_STORAGE (self));
+  g_assert (self->is_operable);
+  g_assert (list);
+  g_assert (indices);
+
+  *num_prefixes = 0;
+
+  sql = "SELECT value FROM hash_prefix WHERE "
+        "threat_type=? AND platform_type=? AND threat_entry_type=? "
+        "ORDER BY value";
+  statement = ephy_sqlite_connection_create_statement (self->db, sql, &error);
+  if (error) {
+    g_warning ("Failed to create select prefix value statement: %s", error->message);
+    goto out;
+  }
+
+  if (!bind_threat_list_params (statement, list, 0, 1, 2, -1))
+    goto out;
+
+  while (ephy_sqlite_statement_step (statement, &error)) {
+    if (g_hash_table_contains (indices, GINT_TO_POINTER (index))) {
+      const guint8 *blob = ephy_sqlite_statement_get_column_as_blob (statement, 0);
+      gsize size = ephy_sqlite_statement_get_column_size (statement, 0);
+      prefixes = g_list_prepend (prefixes, g_bytes_new (blob, size));
+      *num_prefixes += 1;
+    }
+    index++;
+  }
+
+  if (error)
+    g_warning ("Failed to execute select prefix value statement: %s", error->message);
+
+out:
+  if (statement)
+    g_object_unref (statement);
+  if (error)
+    g_error_free (error);
+
+  return prefixes;
+}
+
+static GList *
+ephy_gsb_storage_delete_batch (EphyGSBStorage     *self,
+                               EphyGSBThreatList  *list,
+                               GList              *prefixes,
+                               gsize               num_prefixes)
+{
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  GString *sql;
+
+  g_assert (EPHY_IS_GSB_STORAGE (self));
+  g_assert (self->is_operable);
+  g_assert (list);
+  g_assert (prefixes);
+
+  sql = g_string_new ("DELETE FROM hash_prefix WHERE "
+                      "threat_type=? AND platform_type=? and threat_entry_type=? "
+                      "AND value IN (");
+  for (gsize i = 0; i < num_prefixes; i++)
+    g_string_append (sql, "?,");
+  /* Replace trailing comma character with close parenthesis character. */
+  g_string_overwrite (sql, sql->len - 1, ")");
+
+  statement = ephy_sqlite_connection_create_statement (self->db, sql->str, &error);
+  if (error) {
+    g_warning ("Failed to create delete hash prefix statement: %s", error->message);
+    goto out;
+  }
+
+  if (!bind_threat_list_params (statement, list, 0, 1, 2, -1))
+    goto out;
+
+  for (gsize i = 0; i < num_prefixes; i++) {
+    GBytes *prefix = (GBytes *)prefixes->data;
+    ephy_sqlite_statement_bind_blob (statement, i + 3,
+                                     g_bytes_get_data (prefix, NULL),
+                                     g_bytes_get_size (prefix),
+                                     &error);
+    if (error) {
+      g_warning ("Failed to bind blob in delete hash prefix statement: %s", error->message);
+      goto out;
+    }
+    prefixes = prefixes->next;
+  }
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error)
+    g_warning ("Failed to execute delete hash prefix statement: %s", error->message);
+
+out:
+  g_string_free (sql, TRUE);
+  if (statement)
+    g_object_unref (statement);
+  if (error)
+    g_error_free (error);
+
+  /* Return where we left off. */
+  return prefixes;
+}
+
+void
+ephy_gsb_storage_delete_hash_prefixes (EphyGSBStorage    *self,
+                                       EphyGSBThreatList *list,
+                                       JsonArray         *indices)
+{
+  GList *prefixes = NULL;
+  GList *head = NULL;
+  GHashTable *set;
+  gsize num_prefixes;
+
+  g_return_if_fail (EPHY_IS_GSB_STORAGE (self));
+  g_return_if_fail (self->is_operable);
+  g_return_if_fail (list);
+  g_return_if_fail (indices);
+
+  LOG ("Deleting %u hash prefixes...", json_array_get_length (indices));
+
+  /* Move indices from the JSON array to a hash table set. */
+  set = g_hash_table_new (g_direct_hash, g_direct_equal);
+  for (guint i = 0; i < json_array_get_length (indices); i++)
+    g_hash_table_add (set, GINT_TO_POINTER (json_array_get_int_element (indices, i)));
+
+  prefixes = ephy_gsb_storage_get_hash_prefixes_to_delete (self, list, set, &num_prefixes);
+  head = prefixes;
+
+  for (gsize i = 0; i < num_prefixes / BATCH_SIZE; i++)
+    head = ephy_gsb_storage_delete_batch (self, list, head, BATCH_SIZE);
+
+  if (num_prefixes % BATCH_SIZE != 0)
+    ephy_gsb_storage_delete_batch (self, list, head, num_prefixes % BATCH_SIZE);
+
+  g_hash_table_unref (set);
+  g_list_free_full (prefixes, (GDestroyNotify)g_bytes_unref);
+}
+
+static void
+ephy_gsb_storage_insert_batch (EphyGSBStorage    *self,
+                               EphyGSBThreatList *list,
+                               const guint8      *prefixes,
+                               gsize              start,
+                               gsize              end,
+                               gsize              len)
+{
+  EphySQLiteStatement *statement = NULL;
+  GError *error = NULL;
+  GString *sql;
+  gsize id = 0;
+
+  g_assert (EPHY_IS_GSB_STORAGE (self));
+  g_assert (self->is_operable);
+  g_assert (list);
+  g_assert (prefixes);
+
+  sql = g_string_new ("INSERT INTO hash_prefix "
+                      "(cue, value, threat_type, platform_type, threat_entry_type) VALUES ");
+  for (gsize k = start; k < end; k += len)
+    g_string_append (sql, "(?, ?, ?, ?, ?),");
+  /* Remove trailing comma character. */
+  g_string_erase (sql, sql->len - 1, -1);
+
+  statement = ephy_sqlite_connection_create_statement (self->db, sql->str, &error);
+  g_string_free (sql, TRUE);
+
+  if (error) {
+    g_warning ("Failed to create insert hash prefix statement: %s", error->message);
+    goto out;
+  }
+
+  for (gsize k = start; k < end; k += len) {
+    if (!ephy_sqlite_statement_bind_blob (statement, id++, prefixes + k, 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");
+      goto out;
+    }
+    id += 3;
+  }
+
+  ephy_sqlite_statement_step (statement, &error);
+  if (error)
+    g_warning ("Failed to execute insert hash prefix statement: %s", error->message);
+
+out:
+  if (statement)
+    g_object_unref (statement);
+  if (error)
+    g_error_free (error);
+}
+
+void
+ephy_gsb_storage_insert_hash_prefixes (EphyGSBStorage    *self,
+                                       EphyGSBThreatList *list,
+                                       gsize              prefix_len,
+                                       const char        *prefixes_b64)
+{
+  guint8 *prefixes;
+  gsize prefixes_len;
+  gsize num_batches;
+  gboolean leftovers;
+
+  g_return_if_fail (EPHY_IS_GSB_STORAGE (self));
+  g_return_if_fail (self->is_operable);
+  g_return_if_fail (prefix_len > 0);
+  g_return_if_fail (prefixes_b64);
+
+  prefixes = g_base64_decode (prefixes_b64, &prefixes_len);
+  num_batches = (prefixes_len / prefix_len) / BATCH_SIZE;
+  leftovers = (prefixes_len / prefix_len) % BATCH_SIZE != 0;
+
+  LOG ("Inserting %lu hash prefixes of size %ld...", prefixes_len / prefix_len, prefix_len);
+
+  for (gsize i = 0; i < num_batches; i++) {
+    ephy_gsb_storage_insert_batch (self, list, prefixes,
+                                   i * prefix_len * BATCH_SIZE,
+                                   (i + 1) * prefix_len * BATCH_SIZE,
+                                   prefix_len);
+  }
+
+  if (leftovers) {
+    ephy_gsb_storage_insert_batch (self, list, prefixes,
+                                   num_batches * prefix_len * BATCH_SIZE,
+                                   prefixes_len - 1,
+                                   prefix_len);
+  }
+
+  g_free (prefixes);
+}
diff --git a/lib/safe-browsing/ephy-gsb-storage.h b/lib/safe-browsing/ephy-gsb-storage.h
index 8731ecd..7fb455f 100644
--- a/lib/safe-browsing/ephy-gsb-storage.h
+++ b/lib/safe-browsing/ephy-gsb-storage.h
@@ -20,6 +20,8 @@
 
 #pragma once
 
+#include "ephy-gsb-utils.h"
+
 #include <glib-object.h>
 
 G_BEGIN_DECLS
@@ -28,7 +30,25 @@ G_BEGIN_DECLS
 
 G_DECLARE_FINAL_TYPE (EphyGSBStorage, ephy_gsb_storage, EPHY, GSB_STORAGE, GObject)
 
-EphyGSBStorage *ephy_gsb_storage_new          (const char *db_path);
-gboolean        ephy_gsb_storage_is_operable  (EphyGSBStorage *self);
+EphyGSBStorage *ephy_gsb_storage_new                    (const char *db_path);
+gboolean        ephy_gsb_storage_is_operable            (EphyGSBStorage *self);
+gint64          ephy_gsb_storage_get_next_update_time   (EphyGSBStorage *self);
+void            ephy_gsb_storage_set_next_update_time   (EphyGSBStorage *self,
+                                                         gint64          next_update_time);
+GList          *ephy_gsb_storage_get_threat_lists       (EphyGSBStorage *self);
+char           *ephy_gsb_storage_compute_checksum       (EphyGSBStorage    *self,
+                                                         EphyGSBThreatList *list);
+void            ephy_gsb_storage_update_client_state    (EphyGSBStorage    *self,
+                                                         EphyGSBThreatList *list,
+                                                         gboolean           clear);
+void            ephy_gsb_storage_clear_hash_prefixes    (EphyGSBStorage    *self,
+                                                         EphyGSBThreatList *list);
+void            ephy_gsb_storage_delete_hash_prefixes   (EphyGSBStorage    *self,
+                                                         EphyGSBThreatList *list,
+                                                         JsonArray         *indices);
+void            ephy_gsb_storage_insert_hash_prefixes   (EphyGSBStorage    *self,
+                                                         EphyGSBThreatList *list,
+                                                         gsize              prefix_len,
+                                                         const char        *prefixes_b64);
 
 G_END_DECLS
diff --git a/lib/safe-browsing/ephy-gsb-utils.c b/lib/safe-browsing/ephy-gsb-utils.c
new file mode 100644
index 0000000..5b5aa31
--- /dev/null
+++ b/lib/safe-browsing/ephy-gsb-utils.c
@@ -0,0 +1,108 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <gabrielivascu gnome org>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+#include "ephy-gsb-utils.h"
+
+EphyGSBThreatList *
+ephy_gsb_threat_list_new (const char *threat_type,
+                          const char *platform_type,
+                          const char *threat_entry_type,
+                          const char *client_state,
+                          gint64      timestamp)
+{
+  EphyGSBThreatList *list;
+
+  g_return_val_if_fail (threat_type, NULL);
+  g_return_val_if_fail (platform_type, NULL);
+  g_return_val_if_fail (threat_entry_type, NULL);
+
+  list = g_slice_new (EphyGSBThreatList);
+  list->threat_type = g_strdup (threat_type);
+  list->platform_type = g_strdup (platform_type);
+  list->threat_entry_type = g_strdup (threat_entry_type);
+  list->client_state = g_strdup (client_state);
+  list->timestamp = timestamp;
+
+  return list;
+}
+void
+ephy_gsb_threat_list_free (EphyGSBThreatList *list)
+{
+  g_return_if_fail (list);
+
+  g_free (list->threat_type);
+  g_free (list->platform_type);
+  g_free (list->threat_entry_type);
+  g_free (list->client_state);
+  g_slice_free (EphyGSBThreatList, list);
+}
+
+JsonObject *
+ephy_gsb_utils_make_client_info (void)
+{
+  JsonObject *client_info;
+
+  client_info = json_object_new ();
+  json_object_set_string_member (client_info, "clientId", "Epiphany");
+  json_object_set_string_member (client_info, "clientVersion", VERSION);
+
+  return client_info;
+}
+
+static JsonObject *
+ephy_gsb_utils_make_contraints (void)
+{
+  JsonObject *constraints;
+  JsonArray *compressions;
+
+  compressions = json_array_new ();
+  json_array_add_string_element (compressions, "RAW");
+
+  constraints = json_object_new ();
+  /* No restriction for the number of update entries. */
+  json_object_set_int_member (constraints, "maxUpdateEntries", 0);
+  /* No restriction for the number of database entries. */
+  json_object_set_int_member (constraints, "maxDatabaseEntries", 0);
+  /* Let the server pick the geographic region automatically. */
+  json_object_set_null_member (constraints, "region");
+  /* JsonObject takes ownership of JsonArray. */
+  json_object_set_array_member (constraints, "supportedCompressions", compressions);
+
+  return constraints;
+}
+
+JsonObject *
+ephy_gsb_utils_make_list_update_request (EphyGSBThreatList *list)
+{
+  JsonObject *lur;
+
+  g_return_val_if_fail (list, NULL);
+
+  lur = json_object_new ();
+  json_object_set_string_member (lur, "threatType", list->threat_type);
+  json_object_set_string_member (lur, "platformType", list->platform_type);
+  json_object_set_string_member (lur, "threatEntryType", list->threat_entry_type);
+  json_object_set_string_member (lur, "state", list->client_state);
+  /* JsonObject takes ownership of JsonObject. */
+  json_object_set_object_member (lur, "constraints", ephy_gsb_utils_make_contraints ());
+
+  return lur;
+}
diff --git a/lib/safe-browsing/ephy-gsb-utils.h b/lib/safe-browsing/ephy-gsb-utils.h
new file mode 100644
index 0000000..e1a8a29
--- /dev/null
+++ b/lib/safe-browsing/ephy-gsb-utils.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/*
+ *  Copyright © 2017 Gabriel Ivascu <gabrielivascu gnome org>
+ *
+ *  This file is part of Epiphany.
+ *
+ *  Epiphany is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  Epiphany is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with Epiphany.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <glib.h>
+#include <json-glib/json-glib.h>
+
+G_BEGIN_DECLS
+
+typedef struct {
+  char *threat_type;
+  char *platform_type;
+  char *threat_entry_type;
+  char *client_state;
+  gint64 timestamp;
+} EphyGSBThreatList;
+
+EphyGSBThreatList *ephy_gsb_threat_list_new   (const char *threat_type,
+                                               const char *platform_type,
+                                               const char *threat_entry_type,
+                                               const char *client_state,
+                                               gint64      timestamp);
+void               ephy_gsb_threat_list_free  (EphyGSBThreatList *list);
+
+JsonObject        *ephy_gsb_utils_make_client_info          (void);
+JsonObject        *ephy_gsb_utils_make_list_update_request  (EphyGSBThreatList *list);
+
+G_END_DECLS


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