[epiphany/wip/ephy-sync: 9/48] sync-crypto: Add function to compute Hawk headers
- From: Gabriel - Cristian Ivascu <gabrielivascu src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [epiphany/wip/ephy-sync: 9/48] sync-crypto: Add function to compute Hawk headers
- Date: Sat, 20 Aug 2016 17:04:07 +0000 (UTC)
commit 1a3e053cb2742283319071cb8a987c2499561c7f
Author: Gabriel Ivascu <ivascu gabriel59 gmail com>
Date: Sun Jun 26 18:50:40 2016 +0300
sync-crypto: Add function to compute Hawk headers
src/ephy-sync-crypto.c | 412 +++++++++++++++++++++++++++++++++++++++++++++++
src/ephy-sync-crypto.h | 81 ++++++++--
src/ephy-sync-service.c | 134 ++++++++++++----
src/ephy-sync-utils.c | 36 ++++
src/ephy-sync-utils.h | 4 +
5 files changed, 622 insertions(+), 45 deletions(-)
---
diff --git a/src/ephy-sync-crypto.c b/src/ephy-sync-crypto.c
index 00480cf..64e560d 100644
--- a/src/ephy-sync-crypto.c
+++ b/src/ephy-sync-crypto.c
@@ -18,10 +18,318 @@
#include "ephy-sync-crypto.h"
+#include <libsoup/soup.h>
#include <nettle/hmac.h>
#include <nettle/pbkdf2.h>
+#include <nettle/sha2.h>
#include <string.h>
+#define HAWK_VERSION 1
+
+static EphySyncCryptoHawkHeader *
+ephy_sync_crypto_hawk_header_new (gchar *header,
+ EphySyncCryptoHawkArtifacts *artifacts)
+{
+ EphySyncCryptoHawkHeader *hawk_header;
+
+ hawk_header = g_slice_new (EphySyncCryptoHawkHeader);
+ hawk_header->header = header;
+ hawk_header->artifacts = artifacts;
+
+ return hawk_header;
+}
+
+static EphySyncCryptoHawkArtifacts *
+ephy_sync_crypto_hawk_artifacts_new (gchar *app,
+ gchar *dlg,
+ gchar *ext,
+ gchar *hash,
+ gchar *host,
+ gchar *method,
+ gchar *nonce,
+ gchar *port,
+ gchar *resource,
+ gchar *ts)
+{
+ EphySyncCryptoHawkArtifacts *hawk_artifacts;
+
+ hawk_artifacts = g_slice_new (EphySyncCryptoHawkArtifacts);
+ hawk_artifacts->app = app;
+ hawk_artifacts->dlg = dlg;
+ hawk_artifacts->ext = ext;
+ hawk_artifacts->hash = hash;
+ hawk_artifacts->host = host;
+ hawk_artifacts->method = method;
+ hawk_artifacts->nonce = nonce;
+ hawk_artifacts->port = port;
+ hawk_artifacts->resource = resource;
+ hawk_artifacts->ts = ts;
+
+ return hawk_artifacts;
+}
+
+static void
+ephy_sync_crypto_hawk_artifacts_free (EphySyncCryptoHawkArtifacts *hawk_artifacts)
+{
+ g_free (hawk_artifacts->app);
+ g_free (hawk_artifacts->dlg);
+ g_free (hawk_artifacts->ext);
+ g_free (hawk_artifacts->hash);
+ g_free (hawk_artifacts->host);
+ g_free (hawk_artifacts->method);
+ g_free (hawk_artifacts->nonce);
+ g_free (hawk_artifacts->port);
+ g_free (hawk_artifacts->resource);
+ g_free (hawk_artifacts->ts);
+
+ g_slice_free (EphySyncCryptoHawkArtifacts, hawk_artifacts);
+}
+
+static gchar *
+generate_random_string (gsize length)
+{
+ guchar *bytes;
+ gchar *base64_string;
+ gchar *string;
+
+ bytes = g_malloc (length);
+ for (gsize i = 0; i < length; i++)
+ bytes[i] = g_random_int ();
+
+ base64_string = g_base64_encode (bytes, length);
+ string = g_strndup (base64_string, length);
+
+ g_free (bytes);
+ g_free (base64_string);
+
+ return string;
+}
+
+static gchar *
+find_and_replace_string (const gchar *src,
+ const gchar *find,
+ const gchar *repl)
+{
+ const gchar *haystack = src;
+ const gchar *needle = NULL;
+ gsize haystack_length = strlen (src);
+ gsize find_length = strlen (find);
+ gsize repl_length = strlen (repl);
+ gsize new_length = 0;
+ gsize skip_length = 0;
+ gchar *new = g_malloc (haystack_length + 1);
+
+ while ((needle = g_strstr_len (haystack, -1, find)) != NULL) {
+ haystack_length += find_length - repl_length;
+ new = g_realloc (new, haystack_length + 1);
+ skip_length = needle - haystack;
+ memcpy (new + new_length, haystack, skip_length);
+ memcpy (new + new_length + skip_length, repl, repl_length);
+ new_length += skip_length + repl_length;
+ haystack = needle + find_length;
+ }
+ strcpy (new + new_length, haystack);
+
+ return new;
+}
+
+static gchar *
+normalize_string (const gchar *mac_type,
+ EphySyncCryptoHawkArtifacts *artifacts)
+{
+ gchar *host;
+ gchar *info;
+ gchar *method;
+ gchar *n_ext = NULL;
+ gchar *normalized;
+ gchar *tmp;
+
+ g_return_val_if_fail (mac_type, NULL);
+ g_return_val_if_fail (artifacts, NULL);
+
+ info = g_strdup_printf ("hawk.%d.%s", HAWK_VERSION, mac_type);
+ method = g_ascii_strup (artifacts->method, -1);
+ host = g_ascii_strdown (artifacts->host, -1);
+
+ normalized = g_strjoin ("\n",
+ info,
+ artifacts->ts,
+ artifacts->nonce,
+ method,
+ artifacts->resource,
+ host,
+ artifacts->port,
+ artifacts->hash ? artifacts->hash : "",
+ NULL);
+
+ if (artifacts->ext && strlen (artifacts->ext) > 0) {
+ tmp = find_and_replace_string (artifacts->ext, "\\", "\\\\");
+ n_ext = find_and_replace_string (tmp, "\n", "\\n");
+ g_free (tmp);
+ }
+
+ tmp = normalized;
+ normalized = g_strconcat (normalized, "\n",
+ n_ext ? n_ext : "", "\n",
+ artifacts->app ? artifacts->app : "",
+ artifacts->app ? "\n" : "",
+ artifacts->app && artifacts->dlg ? artifacts->dlg : "",
+ artifacts->app && artifacts->dlg ? "\n" : "",
+ NULL);
+
+ g_free (host);
+ g_free (info);
+ g_free (method);
+ g_free (n_ext);
+ g_free (tmp);
+
+ return normalized;
+}
+
+static gchar *
+parse_content_type (const gchar *content_type)
+{
+ gchar **tokens;
+ gchar *retval;
+
+ tokens = g_strsplit (content_type, ";", -1);
+ retval = g_ascii_strdown (g_strstrip (tokens[0]), -1);
+ g_strfreev (tokens);
+
+ return retval;
+}
+
+static gchar *
+calculate_payload_hash (const gchar *payload,
+ const gchar *content_type)
+{
+ struct sha256_ctx ctx;
+ guint8 *digest;
+ gchar *content;
+ gchar *update;
+ gchar *retval;
+
+ g_return_val_if_fail (payload, NULL);
+ g_return_val_if_fail (content_type, NULL);
+
+ content = parse_content_type (content_type);
+ update = g_strdup_printf ("hawk.%d.payload\n%s\n%s\n",
+ HAWK_VERSION,
+ content,
+ payload);
+ digest = g_malloc (SHA256_DIGEST_SIZE);
+
+ sha256_init (&ctx);
+ sha256_update (&ctx, strlen (update), (guint8 *) update);
+ sha256_digest (&ctx, SHA256_DIGEST_SIZE, digest);
+ retval = g_base64_encode (digest, SHA256_DIGEST_SIZE);
+
+ g_free (content);
+ g_free (update);
+ g_free (digest);
+
+ return retval;
+}
+
+static gchar *
+calculate_mac (const gchar *mac_type,
+ guint8 *key,
+ gsize key_length,
+ EphySyncCryptoHawkArtifacts *artifacts)
+{
+ struct hmac_sha256_ctx ctx;
+ guint8 *digest;
+ gchar *normalized;
+ gchar *mac;
+
+ g_return_val_if_fail (mac_type, NULL);
+ g_return_val_if_fail (key, NULL);
+ g_return_val_if_fail (artifacts, NULL);
+
+ normalized = normalize_string (mac_type, artifacts);
+ digest = g_malloc (SHA256_DIGEST_SIZE);
+
+ hmac_sha256_set_key (&ctx, key_length, key);
+ hmac_sha256_update (&ctx, strlen (normalized), (guint8 *) normalized);
+ hmac_sha256_digest (&ctx, SHA256_DIGEST_SIZE, digest);
+ mac = g_base64_encode (digest, SHA256_DIGEST_SIZE);
+
+ g_free (normalized);
+ g_free (digest);
+
+ return mac;
+}
+
+static gchar *
+append_token_to_header (gchar *header,
+ const gchar *token_name,
+ gchar *token_value)
+{
+ gchar *new_header;
+ gchar *tmp;
+
+ tmp = header;
+ new_header = g_strconcat (header, ", ",
+ token_name, "=\"",
+ token_value, "\"",
+ NULL);
+ g_free (tmp);
+
+ return new_header;
+}
+
+EphySyncCryptoHawkOptions *
+ephy_sync_crypto_hawk_options_new (gchar *app,
+ gchar *dlg,
+ gchar *ext,
+ gchar *content_type,
+ gchar *hash,
+ gchar *local_time_offset,
+ gchar *nonce,
+ gchar *payload,
+ gchar *timestamp)
+{
+ EphySyncCryptoHawkOptions *hawk_options;
+
+ hawk_options = g_slice_new (EphySyncCryptoHawkOptions);
+ hawk_options->app = app;
+ hawk_options->dlg = dlg;
+ hawk_options->ext = ext;
+ hawk_options->content_type = content_type;
+ hawk_options->hash = hash;
+ hawk_options->local_time_offset = local_time_offset;
+ hawk_options->nonce = nonce;
+ hawk_options->payload = payload;
+ hawk_options->timestamp = timestamp;
+
+ return hawk_options;
+}
+
+void
+ephy_sync_crypto_hawk_options_free (EphySyncCryptoHawkOptions *hawk_options)
+{
+ g_free (hawk_options->app);
+ g_free (hawk_options->dlg);
+ g_free (hawk_options->ext);
+ g_free (hawk_options->content_type);
+ g_free (hawk_options->hash);
+ g_free (hawk_options->local_time_offset);
+ g_free (hawk_options->nonce);
+ g_free (hawk_options->payload);
+ g_free (hawk_options->timestamp);
+
+ g_slice_free (EphySyncCryptoHawkOptions, hawk_options);
+}
+
+void
+ephy_sync_crypto_hawk_header_free (EphySyncCryptoHawkHeader *hawk_header)
+{
+ g_free (hawk_header->header);
+ ephy_sync_crypto_hawk_artifacts_free (hawk_header->artifacts);
+
+ g_slice_free (EphySyncCryptoHawkHeader, hawk_header);
+}
+
/*
* Runs 1000 iterations of PBKDF2.
* Uses sha256 as hash function.
@@ -94,3 +402,107 @@ ephy_sync_crypto_hkdf (guint8 *in,
g_free (tmp);
g_free (prk);
}
+
+EphySyncCryptoHawkHeader *
+ephy_sync_crypto_compute_hawk_header (const gchar *url,
+ const gchar *method,
+ const gchar *id,
+ guint8 *key,
+ gsize key_length,
+ EphySyncCryptoHawkOptions *options)
+{
+ EphySyncCryptoHawkArtifacts *artifacts;
+ SoupURI *uri;
+ gboolean has_options;
+ const gchar *hostname;
+ const gchar *resource;
+ gchar *hash;
+ gchar *header;
+ gchar *mac;
+ gchar *nonce;
+ gchar *payload;
+ gchar *timestamp;
+ gint64 ts;
+ guint port;
+
+ g_return_val_if_fail (url && strlen (url) > 0, NULL);
+ g_return_val_if_fail (method && strlen (method) > 0, NULL);
+ g_return_val_if_fail (id && strlen (id) > 0, NULL);
+ g_return_val_if_fail (key, NULL);
+
+ has_options = options != NULL;
+ ts = g_get_real_time () / 1000000;
+
+ timestamp = has_options ? options->timestamp : NULL;
+ if (timestamp) {
+ gchar *local_time_offset;
+ gint64 offset;
+
+ local_time_offset = has_options ? options->local_time_offset : NULL;
+ offset = local_time_offset ? g_ascii_strtoll (local_time_offset, NULL, 10) : 0;
+
+ ts = g_ascii_strtoll (timestamp, NULL, 10) + offset;
+ }
+
+ nonce = has_options ? options->nonce : NULL;
+ nonce = nonce ? nonce : generate_random_string (6);
+ hash = has_options ? options->hash : NULL;
+ payload = has_options ? options->payload : NULL;
+
+ uri = soup_uri_new (url);
+ g_return_val_if_fail (uri, NULL);
+ hostname = soup_uri_get_host (uri);
+ port = soup_uri_get_port (uri);
+ resource = soup_uri_get_path (uri);
+
+ if (!hash && payload) {
+ const gchar *content_type;
+
+ content_type = has_options ? options->content_type : "text/plain";
+ hash = calculate_payload_hash (payload, content_type);
+ }
+
+ artifacts = ephy_sync_crypto_hawk_artifacts_new (has_options ? options->app : NULL,
+ has_options ? options->dlg : NULL,
+ has_options ? options->ext : NULL,
+ hash,
+ g_strdup (hostname),
+ g_strdup (method),
+ nonce,
+ g_strdup_printf ("%u", port),
+ g_strdup (resource),
+ g_strdup_printf ("%ld", ts));
+
+ mac = calculate_mac ("header", key, key_length, artifacts);
+
+ header = g_strconcat ("Hawk id=\"", id, "\"",
+ ", ts=\"", artifacts->ts, "\"",
+ ", nonce=\"", artifacts->nonce, "\"",
+ NULL);
+
+ if (artifacts->hash && strlen (artifacts->hash) > 0)
+ header = append_token_to_header (header, "hash", artifacts->hash);
+
+ if (artifacts->ext && strlen (artifacts->ext) > 0) {
+ gchar *h_ext;
+ gchar *tmp_ext;
+
+ tmp_ext = find_and_replace_string (artifacts->ext, "\\", "\\\\");
+ h_ext = find_and_replace_string (tmp_ext, "\n", "\\n");
+ header = append_token_to_header (header, "ext", h_ext);
+
+ g_free (h_ext);
+ g_free (tmp_ext);
+ }
+
+ header = append_token_to_header (header, "mac", mac);
+
+ if (artifacts->app) {
+ header = append_token_to_header (header, "app", artifacts->app);
+
+ if (artifacts->dlg)
+ header = append_token_to_header (header, "dlg", artifacts->dlg);
+ }
+
+ return ephy_sync_crypto_hawk_header_new (header, artifacts);
+}
diff --git a/src/ephy-sync-crypto.h b/src/ephy-sync-crypto.h
index ec4cd3a..7afb73d 100644
--- a/src/ephy-sync-crypto.h
+++ b/src/ephy-sync-crypto.h
@@ -23,21 +23,72 @@
G_BEGIN_DECLS
-void ephy_sync_crypto_pbkdf2_1k (guint8 *key,
- gsize key_length,
- guint8 *salt,
- gsize salt_length,
- guint8 *out,
- gsize out_length);
-
-void ephy_sync_crypto_hkdf (guint8 *in,
- gsize in_length,
- guint8 *salt,
- gsize salt_length,
- guint8 *info,
- gsize info_length,
- guint8 *out,
- gsize out_length);
+typedef struct {
+ gchar *app;
+ gchar *dlg;
+ gchar *ext;
+ gchar *content_type;
+ gchar *hash;
+ gchar *local_time_offset;
+ gchar *nonce;
+ gchar *payload;
+ gchar *timestamp;
+} EphySyncCryptoHawkOptions;
+
+typedef struct {
+ gchar *app;
+ gchar *dlg;
+ gchar *ext;
+ gchar *hash;
+ gchar *host;
+ gchar *method;
+ gchar *nonce;
+ gchar *port;
+ gchar *resource;
+ gchar *ts;
+} EphySyncCryptoHawkArtifacts;
+
+typedef struct {
+ gchar *header;
+ EphySyncCryptoHawkArtifacts *artifacts;
+} EphySyncCryptoHawkHeader;
+
+void ephy_sync_crypto_pbkdf2_1k (guint8 *key,
+ gsize key_length,
+ guint8 *salt,
+ gsize salt_length,
+ guint8 *out,
+ gsize out_length);
+
+void ephy_sync_crypto_hkdf (guint8 *in,
+ gsize in_length,
+ guint8 *salt,
+ gsize salt_length,
+ guint8 *info,
+ gsize info_length,
+ guint8 *out,
+ gsize out_length);
+
+EphySyncCryptoHawkHeader *ephy_sync_crypto_compute_hawk_header (const gchar *url,
+ const gchar *method,
+ const gchar *id,
+ guint8 *key,
+ gsize key_length,
+ EphySyncCryptoHawkOptions *options);
+
+EphySyncCryptoHawkOptions *ephy_sync_crypto_hawk_options_new (gchar *app,
+ gchar *dlg,
+ gchar *ext,
+ gchar *content_type,
+ gchar *hash,
+ gchar *local_time_offset,
+ gchar *nonce,
+ gchar *payload,
+ gchar *timestamp);
+
+void ephy_sync_crypto_hawk_options_free (EphySyncCryptoHawkOptions *hawk_options);
+
+void ephy_sync_crypto_hawk_header_free (EphySyncCryptoHawkHeader *hawk_header);
G_END_DECLS
diff --git a/src/ephy-sync-service.c b/src/ephy-sync-service.c
index a074281..04910e1 100644
--- a/src/ephy-sync-service.c
+++ b/src/ephy-sync-service.c
@@ -28,7 +28,8 @@
#include <libsoup/soup.h>
#include <string.h>
-#define BASEURL "https://api.accounts.firefox.com/v1"
+#define FXA_BASEURL "https://api.accounts.firefox.com/"
+#define FXA_VERSION "v1/"
struct _EphySyncService {
GObject parent_instance;
@@ -42,13 +43,107 @@ struct _EphySyncService {
G_DEFINE_TYPE (EphySyncService, ephy_sync_service, G_TYPE_OBJECT);
+static SoupMessage *
+synchronous_hawk_get_request (EphySyncService *self,
+ const gchar *endpoint,
+ const gchar *id,
+ guint8 *key,
+ gsize key_length)
+{
+ EphySyncCryptoHawkHeader *hawk_header;
+ SoupMessage *message;
+ gchar *url;
+
+ url = g_strdup_printf ("%s%s%s", FXA_BASEURL, FXA_VERSION, endpoint);
+ message = soup_message_new (SOUP_METHOD_GET, url);
+ hawk_header = ephy_sync_crypto_compute_hawk_header (url, "GET",
+ id,
+ key, key_length,
+ NULL);
+ soup_message_headers_append (message->request_headers,
+ "authorization", hawk_header->header);
+LOG ("[%d] Sending synchronous HAWK GET request to %s endpoint", __LINE__, endpoint);
+ soup_session_send_message (self->soup_session, message);
+
+ g_free (url);
+ ephy_sync_crypto_hawk_header_free (hawk_header);
+
+ return message;
+}
+
+static SoupMessage *
+synchronous_hawk_post_request (EphySyncService *self,
+ const gchar *endpoint,
+ const gchar *id,
+ guint8 *key,
+ gsize key_length,
+ gchar *request_body)
+{
+ EphySyncCryptoHawkHeader *hawk_header;
+ EphySyncCryptoHawkOptions *hawk_options;
+ SoupMessage *message;
+ gchar *url;
+
+ url = g_strdup_printf ("%s%s%s", FXA_BASEURL, FXA_VERSION, endpoint);
+ message = soup_message_new (SOUP_METHOD_POST, url);
+ soup_message_set_request (message,
+ "application/json",
+ SOUP_MEMORY_TAKE,
+ request_body,
+ strlen (request_body));
+ hawk_options = ephy_sync_crypto_hawk_options_new (NULL, NULL, NULL,
+ g_strdup ("application/json"),
+ NULL, NULL, NULL,
+ g_strdup (request_body),
+ NULL);
+ hawk_header = ephy_sync_crypto_compute_hawk_header (url, "POST",
+ id,
+ key, key_length,
+ hawk_options);
+ soup_message_headers_append (message->request_headers,
+ "authorization", hawk_header->header);
+ soup_message_headers_append (message->request_headers,
+ "content-type", "application/json");
+LOG ("[%d] Sending synchronous HAWK POST request to %s endpoint", __LINE__, endpoint);
+ soup_session_send_message (self->soup_session, message);
+
+ g_free (url);
+ ephy_sync_crypto_hawk_options_free (hawk_options);
+ ephy_sync_crypto_hawk_header_free (hawk_header);
+
+ return message;
+}
+
+static SoupMessage *
+synchronous_fxa_post_request (EphySyncService *self,
+ const gchar *endpoint,
+ gchar *request_body)
+{
+ SoupMessage *message;
+ gchar *url;
+
+ url = g_strdup_printf ("%s%s%s", FXA_BASEURL, FXA_VERSION, endpoint);
+ message = soup_message_new (SOUP_METHOD_POST, url);
+ soup_message_set_request (message,
+ "application/json",
+ SOUP_MEMORY_TAKE,
+ request_body,
+ strlen (request_body));
+LOG ("[%d] Sending synchronous POST request to %s endpoint", __LINE__, endpoint);
+ soup_session_send_message (self->soup_session, message);
+
+ g_free (url);
+
+ return message;
+}
+
static void
ephy_sync_service_finalize (GObject *object)
{
EphySyncService *self = EPHY_SYNC_SERVICE (object);
g_free (self->user_email);
- g_clear_object (&self->tokens);
+ g_hash_table_destroy (self->tokens);
g_clear_object (&self->soup_session);
G_OBJECT_CLASS (ephy_sync_service_parent_class)->finalize (object);
@@ -67,7 +162,7 @@ ephy_sync_service_init (EphySyncService *self)
{
gchar *sync_user = NULL;
- self->tokens = g_hash_table_new_full (NULL, g_str_equal,
+ self->tokens = g_hash_table_new_full (g_str_hash, g_str_equal,
NULL, g_free);
self->soup_session = soup_session_new ();
@@ -82,26 +177,6 @@ ephy_sync_service_init (EphySyncService *self)
LOG ("[%d] sync service inited", __LINE__);
}
-static SoupMessage *
-synchronous_post_request (EphySyncService *self,
- const gchar *endpoint,
- gchar *request_body)
-{
- SoupMessage *message;
-
- message = soup_message_new (SOUP_METHOD_POST,
- g_strdup_printf ("%s/%s", BASEURL, endpoint));
- soup_message_set_request (message,
- "application/json",
- SOUP_MEMORY_TAKE,
- request_body,
- strlen (request_body));
-LOG ("[%d] Sending synchronous POST request to %s endpoint", __LINE__, endpoint);
- soup_session_send_message (self->soup_session, message);
-
- return message;
-}
-
EphySyncService *
ephy_sync_service_new (void)
{
@@ -194,13 +269,12 @@ ephy_sync_service_login (EphySyncService *self,
authPW = ephy_sync_service_get_token (self, EPHY_SYNC_TOKEN_AUTHPW);
g_return_val_if_fail (authPW, FALSE);
- request_body = g_strconcat ("{\"authPW\": \"", authPW,
- "\", \"email\": \"", self->user_email,
- "\"}", NULL);
-
- message = synchronous_post_request (self,
- "account/login?keys=true",
- request_body);
+ request_body = ephy_sync_utils_build_json_string ("authPW", authPW,
+ "email", self->user_email,
+ NULL);
+ message = synchronous_fxa_post_request (self,
+ "account/login?keys=true",
+ request_body);
LOG ("[%d] status code: %u", __LINE__, message->status_code);
parser = json_parser_new ();
diff --git a/src/ephy-sync-utils.c b/src/ephy-sync-utils.c
index 68d2f36..c307b0b 100644
--- a/src/ephy-sync-utils.c
+++ b/src/ephy-sync-utils.c
@@ -93,6 +93,42 @@ ephy_sync_utils_token_name_from_type (EphySyncTokenType token_type)
}
}
+gchar *
+ephy_sync_utils_build_json_string (const gchar *first_key,
+ const gchar *first_value,
+ ...)
+{
+ va_list args;
+ gchar *json;
+ gchar *key;
+ gchar *value;
+ gchar *tmp;
+
+ json = g_strconcat ("{\"",
+ first_key, "\": \"",
+ first_value, "\"",
+ NULL);
+
+ va_start (args, first_value);
+ while ((key = va_arg (args, gchar *)) != NULL) {
+ value = va_arg (args, gchar *);
+
+ tmp = json;
+ json = g_strconcat (json, ", \"",
+ key, "\": \"",
+ value, "\"",
+ NULL);
+ g_free (tmp);
+ }
+ va_end (args);
+
+ tmp = json;
+ json = g_strconcat (json, "}", NULL);
+ g_free (tmp);
+
+ return json;
+}
+
/* FIXME: Only for debugging, remove when no longer needed */
void
ephy_sync_utils_display_hex (const gchar *data_name,
diff --git a/src/ephy-sync-utils.h b/src/ephy-sync-utils.h
index d3eeae6..9e33850 100644
--- a/src/ephy-sync-utils.h
+++ b/src/ephy-sync-utils.h
@@ -46,6 +46,10 @@ guint8 *ephy_sync_utils_decode_hex (const gchar *hex_string);
const gchar *ephy_sync_utils_token_name_from_type (EphySyncTokenType token_type);
+gchar *ephy_sync_utils_build_json_string (const gchar *first_key,
+ const gchar *first_value,
+ ...) G_GNUC_NULL_TERMINATED;
+
/* FIXME: Only for debugging, remove when no longer needed */
void ephy_sync_utils_display_hex (const gchar *data_name,
guint8 *data,
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]