summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGabriel Ivascu <gabrielivascu@gnome.org>2017-09-12 20:19:25 +0300
committerGabriel Ivascu <gabrielivascu@gnome.org>2017-10-03 18:29:53 +0200
commit59e83d94ef0a376ade9c962e7b84142499522351 (patch)
tree1fbb132fd1b70068a7571c5a6cfb88ca4f383321
parentcfe43c63018665010d4f4366fa03473ced8d7bee (diff)
downloadepiphany-59e83d94ef0a376ade9c962e7b84142499522351.tar.gz
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.
-rw-r--r--data/org.gnome.epiphany.gschema.xml10
-rw-r--r--embed/ephy-embed-shell.c5
-rw-r--r--lib/ephy-prefs.h2
-rw-r--r--lib/meson.build2
-rw-r--r--lib/safe-browsing/ephy-gsb-service.c209
-rw-r--r--lib/safe-browsing/ephy-gsb-service.h3
-rw-r--r--lib/safe-browsing/ephy-gsb-storage.c645
-rw-r--r--lib/safe-browsing/ephy-gsb-storage.h25
-rw-r--r--lib/safe-browsing/ephy-gsb-utils.c128
-rw-r--r--lib/safe-browsing/ephy-gsb-utils.h44
10 files changed, 1047 insertions, 26 deletions
diff --git a/data/org.gnome.epiphany.gschema.xml b/data/org.gnome.epiphany.gschema.xml
index b0319e70e..78de54ac8 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 47541ded2..9003701d2 100644
--- a/embed/ephy-embed-shell.c
+++ b/embed/ephy-embed-shell.c
@@ -591,11 +591,14 @@ ephy_embed_shell_get_global_gsb_service (EphyEmbedShell *shell)
g_return_val_if_fail (EPHY_IS_EMBED_SHELL (shell), NULL);
if (priv->global_gsb_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->global_gsb_service = ephy_gsb_service_new (filename);
+ priv->global_gsb_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 4d565e64c..3f359209f 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 fdb5d75a2..6244e7033 100644
--- a/lib/meson.build
+++ b/lib/meson.build
@@ -50,6 +50,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
]
@@ -62,6 +63,7 @@ libephymisc_deps = [
gnome_desktop_dep,
gtk_dep,
icu_uc_dep,
+ json_glib_dep,
libdazzle_dep,
libsecret_dep,
libsoup_dep,
diff --git a/lib/safe-browsing/ephy-gsb-service.c b/lib/safe-browsing/ephy-gsb-service.c
index dda2add02..2a3ce428e 100644
--- a/lib/safe-browsing/ephy-gsb-service.c
+++ b/lib/safe-browsing/ephy-gsb-service.c
@@ -23,23 +23,186 @@
#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)
+{
+ JsonNode *node;
+
+ node = json_object_get_member (object, member);
+ if (!node || !JSON_NODE_HOLDS_VALUE (node))
+ return FALSE;
+
+ return json_node_get_string (node) != NULL;
+}
+
+static inline gboolean
+json_object_has_non_null_array_member (JsonObject *object,
+ const char *member)
+{
+ JsonNode *node;
+
+ node = json_object_get_member (object, member);
+ if (!node)
+ return FALSE;
+
+ return JSON_NODE_HOLDS_ARRAY (node);
+}
+
+static void
+update_threat_lists_cb (SoupSession *session,
+ SoupMessage *msg,
+ gpointer user_data)
+{
+ EphyGSBService *self = EPHY_GSB_SERVICE (user_data);
+ 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, NULL);
+ 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 state...");
+ 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;
+ 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;
+
+ body = ephy_gsb_utils_make_list_updates_request (threat_lists);
+ 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);
+ 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 +212,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 +235,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 +247,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 +271,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 +301,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 +321,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 bcb407292..83713949d 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 6b7c2de07..27edf0a03 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,11 @@ 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;
+ gboolean retval = FALSE;
g_assert (EPHY_IS_GSB_STORAGE (self));
g_assert (EPHY_IS_SQLITE_CONNECTION (self->db));
@@ -98,21 +172,58 @@ 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;
+ sql = "INSERT INTO threats (threat_type, platform_type, threat_entry_type) VALUES ";
+ string = g_string_new (sql);
+ for (guint i = 0; i < G_N_ELEMENTS (gsb_linux_threat_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 (guint i = 0; i < G_N_ELEMENTS (gsb_linux_threat_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 +240,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,9 +275,9 @@ 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)),"
"PRIMARY KEY (value, threat_type, platform_type, threat_entry_type)"
@@ -207,12 +318,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:
@@ -277,7 +394,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;
}
@@ -408,3 +525,493 @@ 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_assert (EPHY_IS_GSB_STORAGE (self));
+ g_assert (self->is_operable);
+
+ 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_assert (EPHY_IS_GSB_STORAGE (self));
+ g_assert (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_assert (EPHY_IS_GSB_STORAGE (self));
+ g_assert (self->is_operable);
+
+ 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 g_list_reverse (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_assert (EPHY_IS_GSB_STORAGE (self));
+ g_assert (self->is_operable);
+ g_assert (list);
+
+ 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_assert (EPHY_IS_GSB_STORAGE (self));
+ g_assert (self->is_operable);
+ g_assert (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_assert (EPHY_IS_GSB_STORAGE (self));
+ g_assert (self->is_operable);
+ g_assert (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;
+ guint 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_assert (EPHY_IS_GSB_STORAGE (self));
+ g_assert (self->is_operable);
+ g_assert (list);
+ g_assert (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_assert (EPHY_IS_GSB_STORAGE (self));
+ g_assert (self->is_operable);
+ g_assert (list);
+ g_assert (prefix_len > 0);
+ g_assert (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 8731ecd81..31a9f3120 100644
--- a/lib/safe-browsing/ephy-gsb-storage.h
+++ b/lib/safe-browsing/ephy-gsb-storage.h
@@ -20,7 +20,10 @@
#pragma once
+#include "ephy-gsb-utils.h"
+
#include <glib-object.h>
+#include <json-glib/json-glib.h>
G_BEGIN_DECLS
@@ -28,7 +31,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 000000000..8cef2e8a9
--- /dev/null
+++ b/lib/safe-browsing/ephy-gsb-utils.c
@@ -0,0 +1,128 @@
+/* -*- 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"
+
+#include <json-glib/json-glib.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_assert (threat_type);
+ g_assert (platform_type);
+ g_assert (threat_entry_type);
+
+ 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_assert (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);
+}
+
+static 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");
+ json_object_set_array_member (constraints, "supportedCompressions", compressions);
+
+ return constraints;
+}
+
+char *
+ephy_gsb_utils_make_list_updates_request (GList *threat_lists)
+{
+ JsonArray *requests;
+ JsonObject *body_obj;
+ JsonNode *body_node;
+ char *retval;
+
+ g_assert (threat_lists);
+
+ requests = json_array_new ();
+ for (GList *l = threat_lists; l && l->data; l = l->next) {
+ EphyGSBThreatList *list = (EphyGSBThreatList *)l->data;
+ JsonObject *request = json_object_new ();
+
+ json_object_set_string_member (request, "threatType", list->threat_type);
+ json_object_set_string_member (request, "platformType", list->platform_type);
+ json_object_set_string_member (request, "threatEntryType", list->threat_entry_type);
+ json_object_set_string_member (request, "state", list->client_state);
+ json_object_set_object_member (request, "constraints", ephy_gsb_utils_make_contraints ());
+ json_array_add_object_element (requests, request);
+ }
+
+ 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);
+ retval = json_to_string (body_node, FALSE);
+
+ json_object_unref (body_obj);
+ json_node_unref (body_node);
+
+ return retval;
+}
diff --git a/lib/safe-browsing/ephy-gsb-utils.h b/lib/safe-browsing/ephy-gsb-utils.h
new file mode 100644
index 000000000..e7906fb2e
--- /dev/null
+++ b/lib/safe-browsing/ephy-gsb-utils.h
@@ -0,0 +1,44 @@
+/* -*- 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>
+
+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);
+
+char *ephy_gsb_utils_make_list_updates_request (GList *threat_lists);
+
+G_END_DECLS