summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorJuan Pablo Ugarte <juanpablougarte@gmail.com>2020-07-10 17:52:47 -0300
committerJuan Pablo Ugarte <juanpablougarte@gmail.com>2020-07-10 19:26:46 -0300
commit50d465281fc05d08a8c814d64bc479b7ef8eb858 (patch)
treed4597899c158f449432ead6e67cbb11820f33f42 /src
parent87fbeba96aef3d7013a2abc587131539c86166ec (diff)
downloadglade-50d465281fc05d08a8c814d64bc479b7ef8eb858.tar.gz
GladeHttp: add cookies support
Add cookies support. Implement chunked transfer encoding. Add response paremeter to request-done signal.
Diffstat (limited to 'src')
-rw-r--r--src/glade-http.c317
-rw-r--r--src/glade-http.h17
2 files changed, 232 insertions, 102 deletions
diff --git a/src/glade-http.c b/src/glade-http.c
index 849409d7..eafba25c 100644
--- a/src/glade-http.c
+++ b/src/glade-http.c
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2014 Juan Pablo Ugarte.
+ * Copyright (C) 2014, 2020 Juan Pablo Ugarte.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as
@@ -23,8 +23,6 @@
#include <string.h>
#include <stdio.h>
-#define HTTP_BUFFER_SIZE 16
-
struct _GladeHTTPPrivate
{
gchar *host;
@@ -33,12 +31,23 @@ struct _GladeHTTPPrivate
GladeHTTPStatus status;
GSocketConnection *connection;
+ GDataInputStream *data_stream;
GCancellable *cancellable;
-
+
GString *data;
GString *response;
- gchar response_buffer[HTTP_BUFFER_SIZE];
+ gint http_major;
+ gint http_minor;
+ gint code;
+
+ gchar *transfer_encoding_value;
+ gchar *content_length_value;
+
+ GHashTable *cookies;
+
+ gint content_length;
+ gboolean chunked;
};
enum
@@ -65,55 +74,48 @@ static guint http_signals[LAST_SIGNAL] = { 0 };
G_DEFINE_TYPE_WITH_PRIVATE (GladeHTTP, glade_http, G_TYPE_OBJECT);
static void
-glade_http_emit_request_done (GladeHTTP *http, gchar *response)
+glade_http_close (GladeHTTP *http)
{
- gint http_major, http_minor, code, i;
- gchar **headers, **values, *blank, *data, tmp;
+ GladeHTTPPrivate *priv = http->priv;
- if ((blank = g_strstr_len (response, -1, "\r\n\r\n")))
- data = blank + 4;
- else if ((blank = g_strstr_len (response, -1, "\n\n")))
- data = blank + 2;
- else
- return;
+ g_clear_object (&priv->data_stream);
+ g_clear_object (&priv->connection);
+ g_clear_object (&priv->cancellable);
+}
- tmp = *blank;
- *blank = '\0';
- headers = g_strsplit (response, "\n", 0);
- *blank = tmp;
+static void
+glade_http_clear (GladeHTTP *http)
+{
+ GladeHTTPPrivate *priv = http->priv;
- values = g_new0 (gchar *, g_strv_length (headers));
+ glade_http_close (http);
- for (i = 0; headers[i]; i++)
- {
- g_strchug (headers[i]);
+ g_string_assign (priv->data, "");
+ g_string_assign (priv->response, "");
- if (i)
- {
- gchar *colon = g_strstr_len (headers[i], -1, ":");
+ priv->http_major = priv->http_minor = priv->code = 0;
- if (colon)
- {
- *colon++ = '\0';
- values[i-1] = g_strstrip (colon);
- }
- else
- values[i-1] = "";
- }
- else
- {
- if (sscanf (response, "HTTP/%d.%d %d", &http_major, &http_minor, &code) != 3)
- http_major = http_minor = code = 0;
- }
- }
+ g_clear_pointer (&priv->transfer_encoding_value, g_free);
+ g_clear_pointer (&priv->content_length_value, g_free);
+
+ priv->chunked = FALSE;
+ priv->content_length = 0;
+
+ g_hash_table_remove_all (priv->cookies);
+
+ priv->status = GLADE_HTTP_READY;
+}
+
+static void
+glade_http_emit_request_done (GladeHTTP *http)
+{
+ GladeHTTPPrivate *priv = http->priv;
- /* emit request-done */
- g_signal_emit (http, http_signals[REQUEST_DONE], 0, code,
- (headers[0]) ? &headers[1] : NULL, values,
- data);
+ g_file_set_contents ("response.html", priv->response->str, -1, NULL);
- g_strfreev (headers);
- g_free (values);
+ /* emit request-done */
+ g_signal_emit (http, http_signals[REQUEST_DONE], 0,
+ priv->code, priv->response->str);
}
static void
@@ -129,49 +131,143 @@ glade_http_emit_status (GladeHTTP *http, GladeHTTPStatus status, GError *error)
g_signal_emit (http, http_signals[STATUS], 0, status, error);
}
+static gboolean
+parse_cookie (const gchar *str, gchar **key, gchar **value)
+{
+ gchar **tokens, *colon;
+
+ if (str == NULL)
+ return FALSE;
+
+ tokens = g_strsplit(str, "=", 2);
+
+ if ((colon = g_strstr_len (tokens[1], -1, ";")))
+ *colon = '\0';
+
+ *key = g_strstrip (tokens[0]);
+ *value = g_strstrip (tokens[1]);
+
+ g_free (tokens);
+
+ return TRUE;
+}
+
static void
-on_read_ready (GObject *source, GAsyncResult *res, gpointer data)
+on_read_line_ready (GObject *source, GAsyncResult *res, gpointer data)
{
GladeHTTPPrivate *priv = GLADE_HTTP (data)->priv;
- GError *error = NULL;
- gssize bytes_read;
+ g_autoptr (GError) error = NULL;
+ g_autofree gchar *line;
+ gsize length;
glade_http_emit_status (data, GLADE_HTTP_RECEIVING, NULL);
- bytes_read = g_input_stream_read_finish (G_INPUT_STREAM (source), res, &error);
-
- if (bytes_read > 0)
- {
- g_string_append_len (priv->response, priv->response_buffer, bytes_read);
-
- /* NOTE: We do not need to parse content-length because we do not support
- * multiples HTTP requests in the same connection.
- */
- if (priv->cancellable)
- g_cancellable_reset (priv->cancellable);
-
- g_input_stream_read_async (g_io_stream_get_input_stream (G_IO_STREAM (priv->connection)),
- priv->response_buffer, HTTP_BUFFER_SIZE,
- G_PRIORITY_DEFAULT, priv->cancellable,
- on_read_ready, data);
- return;
- }
- else if (bytes_read < 0)
+ line = g_data_input_stream_read_line_finish (priv->data_stream, res, &length, &error);
+
+ if (error)
{
glade_http_emit_status (data, GLADE_HTTP_ERROR, error);
g_error_free (error);
- return;
}
- /* emit request-done */
- glade_http_emit_request_done (data, priv->response->str);
- glade_http_emit_status (data, GLADE_HTTP_READY, NULL);
+ if (!line)
+ return;
+
+ if (priv->http_major == 0)
+ {
+ /* Reading HTTP version */
+ if (sscanf (line, "HTTP/%d.%d %d",
+ &priv->http_major,
+ &priv->http_minor,
+ &priv->code) != 3)
+ {
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ "Could not parse HTTP header");
+ glade_http_emit_status (data, GLADE_HTTP_ERROR, error);
+ return;
+ }
+ }
+ else if (priv->response->len < priv->content_length)
+ {
+ /* We are reading a chunk or the whole response */
+ g_string_append_len (priv->response, line, length);
+ }
+ else
+ {
+ /* Reading HTTP Headers */
+
+ if (priv->chunked)
+ {
+ gint chunk;
+
+ if (sscanf (line, "%x", &chunk) != 1)
+ {
+ error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_INVALID_DATA,
+ "Could not parse chunk size");
+ glade_http_emit_status (data, GLADE_HTTP_ERROR, error);
+ return;
+ }
+
+ priv->content_length += chunk;
+ }
+ /* Check if this is the last line of the headers */
+ else if (length == 0)
+ {
+ if (g_strcmp0 (priv->transfer_encoding_value, "chunked") == 0)
+ priv->chunked = TRUE;
+ else
+ priv->content_length = atoi (priv->content_length_value);
+ }
+ else
+ {
+ g_auto(GStrv) tokens = g_strsplit (line, ":", 2);
+
+ if (tokens)
+ {
+ gchar *key = g_strstrip (tokens[0]);
+ gchar *val = g_strstrip (tokens[1]);
+
+ if (g_strcmp0 ("Transfer-Encoding", key) == 0)
+ {
+ priv->transfer_encoding_value = g_strdup (val);
+ }
+ else if (g_strcmp0 ("Content-Length", key) == 0)
+ {
+ priv->content_length_value = g_strdup (val);
+ }
+ else if (g_strcmp0 ("Set-Cookie", key) == 0)
+ {
+ gchar *cookie = NULL, *value = NULL;
+
+ /* Take cookie/value ownership */
+ if (parse_cookie (val, &cookie, &value))
+ g_hash_table_insert (priv->cookies, cookie, value);
+ }
+ }
+ }
+ }
+
+ if (priv->content_length && priv->response->len >= priv->content_length)
+ {
+ glade_http_close (data);
+
+ /* emit request-done */
+ glade_http_emit_request_done (data);
+ glade_http_emit_status (data, GLADE_HTTP_READY, NULL);
+ }
+ else
+ g_data_input_stream_read_line_async (priv->data_stream,
+ G_PRIORITY_DEFAULT,
+ priv->cancellable,
+ on_read_line_ready,
+ data);
}
static void
on_write_ready (GObject *source, GAsyncResult *res, gpointer data)
{
GladeHTTPPrivate *priv = GLADE_HTTP (data)->priv;
+ GInputStream *input_stream;
GError *error = NULL;
gsize count;
@@ -190,9 +286,20 @@ on_write_ready (GObject *source, GAsyncResult *res, gpointer data)
}
glade_http_emit_status (data, GLADE_HTTP_WAITING, NULL);
- g_input_stream_read_async (g_io_stream_get_input_stream (G_IO_STREAM (priv->connection)),
- priv->response_buffer, HTTP_BUFFER_SIZE,
- G_PRIORITY_DEFAULT, priv->cancellable, on_read_ready, data);
+
+ /* Get connection stream */
+ input_stream = g_io_stream_get_input_stream (G_IO_STREAM (priv->connection));
+
+ /* Create stream to read structured data */
+ priv->data_stream = g_data_input_stream_new (input_stream);
+ g_data_input_stream_set_newline_type (priv->data_stream, G_DATA_STREAM_NEWLINE_TYPE_CR_LF);
+
+ /* Start reading headers line by line */
+ g_data_input_stream_read_line_async (priv->data_stream,
+ G_PRIORITY_DEFAULT,
+ priv->cancellable,
+ on_read_line_ready,
+ data);
}
static void
@@ -226,28 +333,12 @@ glade_http_init (GladeHTTP *http)
priv = http->priv = glade_http_get_instance_private (http);
+ priv->cookies = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
priv->data = g_string_new ("");
priv->response = g_string_new ("");
}
static void
-glade_http_clear (GladeHTTP *http)
-{
- GladeHTTPPrivate *priv = http->priv;
-
- if (priv->cancellable)
- g_cancellable_cancel (priv->cancellable);
-
- g_clear_object (&priv->connection);
- g_clear_object (&priv->cancellable);
-
- g_string_assign (priv->data, "");
- g_string_assign (priv->response, "");
-
- priv->status = GLADE_HTTP_READY;
-}
-
-static void
glade_http_finalize (GObject *object)
{
GladeHTTPPrivate *priv = GLADE_HTTP (object)->priv;
@@ -256,7 +347,8 @@ glade_http_finalize (GObject *object)
g_string_free (priv->data, TRUE);
g_string_free (priv->response, TRUE);
-
+ g_hash_table_destroy (priv->cookies);
+
G_OBJECT_CLASS (glade_http_parent_class)->finalize (object);
}
@@ -340,11 +432,11 @@ glade_http_class_init (GladeHTTPClass *klass)
http_signals[REQUEST_DONE] =
g_signal_new ("request-done",
- G_OBJECT_CLASS_TYPE (klass), 0,
+ G_OBJECT_CLASS_TYPE (klass), G_SIGNAL_RUN_FIRST,
G_STRUCT_OFFSET (GladeHTTPClass, request_done),
NULL, NULL, NULL,
- G_TYPE_NONE, 4,
- G_TYPE_INT, G_TYPE_STRV, G_TYPE_STRV, G_TYPE_STRING);
+ G_TYPE_NONE, 2,
+ G_TYPE_INT, G_TYPE_STRING);
http_signals[STATUS] =
g_signal_new ("status",
@@ -379,6 +471,41 @@ glade_http_get_port (GladeHTTP *http)
return http->priv->port;
}
+gchar *
+glade_http_get_cookie (GladeHTTP *http, const gchar *key)
+{
+ g_return_val_if_fail (GLADE_IS_HTTP (http), NULL);
+ return g_hash_table_lookup (http->priv->cookies, key);
+}
+
+static void
+collect_cookies (gpointer key, gpointer value, gpointer user_data)
+{
+ GString *cookies = user_data;
+
+ if (cookies->len)
+ g_string_append_printf (cookies, "; %s=%s", (gchar *)key, (gchar *)value);
+ else
+ g_string_append_printf (cookies, "%s=%s", (gchar *)key, (gchar *)value);
+}
+
+gchar *
+glade_http_get_cookies (GladeHTTP *http)
+{
+ GString *cookies;
+ gchar *retval;
+
+ g_return_val_if_fail (GLADE_IS_HTTP (http), NULL);
+
+ cookies = g_string_new ("");
+ g_hash_table_foreach (http->priv->cookies, collect_cookies, cookies);
+
+ retval = cookies->str;
+ g_string_free (cookies, FALSE);
+
+ return retval;
+}
+
void
glade_http_request_send_async (GladeHTTP *http,
GCancellable *cancellable,
diff --git a/src/glade-http.h b/src/glade-http.h
index 05b450c1..f64e0166 100644
--- a/src/glade-http.h
+++ b/src/glade-http.h
@@ -52,15 +52,13 @@ struct _GladeHTTPClass
GObjectClass parent_class;
/* Signals */
- void (*request_done) (GladeHTTP *self,
- gint code,
- const gchar **headers,
- const gchar **values,
+ void (*request_done) (GladeHTTP *self,
+ gint code,
const gchar *response);
- void (*status) (GladeHTTP *self,
- GladeHTTPStatus status,
- GError *error);
+ void (*status) (GladeHTTP *self,
+ GladeHTTPStatus status,
+ GError *error);
};
struct _GladeHTTP
@@ -79,6 +77,11 @@ GladeHTTP *glade_http_new (const gchar *host,
const gchar *glade_http_get_host (GladeHTTP *http);
gint glade_http_get_port (GladeHTTP *http);
+gchar *glade_http_get_cookie (GladeHTTP *http,
+ const gchar *key);
+
+gchar *glade_http_get_cookies (GladeHTTP *http);
+
void glade_http_request_send_async (GladeHTTP *http,
GCancellable *cancellable,
const gchar *format,