about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/command/command.c13
-rw-r--r--src/command/commands.c42
-rw-r--r--src/main.c1
-rw-r--r--src/server_events.c12
-rw-r--r--src/server_events.h2
-rw-r--r--src/ui/core.c114
-rw-r--r--src/ui/ui.h1
-rw-r--r--src/ui/window.h1
-rw-r--r--src/ui/windows.c15
-rw-r--r--src/xmpp/capabilities.c13
-rw-r--r--src/xmpp/form.c258
-rw-r--r--src/xmpp/form.h40
-rw-r--r--src/xmpp/iq.c84
-rw-r--r--src/xmpp/stanza.c135
-rw-r--r--src/xmpp/stanza.h15
-rw-r--r--src/xmpp/xmpp.h28
16 files changed, 655 insertions, 119 deletions
diff --git a/src/command/command.c b/src/command/command.c
index 6d707afe..f6080468 100644
--- a/src/command/command.c
+++ b/src/command/command.c
@@ -306,10 +306,13 @@ static struct cmd_t command_defs[] =
 
     { "/room",
         cmd_room, parse_args, 2, 2, NULL,
-        { "/room config accept|cancel", "Room configuration.",
-        { "/room config accept|cncel",
-          "-------------------------",
-          "Accept or cancel default room configuration.",
+        { "/room config accept|destroy|edit|cancel", "Room configuration.",
+        { "/room config accept|destroy|edit|cancel",
+          "---------------------------------------",
+          "config accept  - Accept default room configuration.",
+          "config destroy - Cancel default room configuration.",
+          "config edit    - Edit room configuration.",
+          "config cancel  - Cancel room configuration.",
           NULL } } },
 
     { "/rooms",
@@ -1210,6 +1213,8 @@ cmd_init(void)
 
     room_config_ac = autocomplete_new();
     autocomplete_add(room_config_ac, "accept");
+    autocomplete_add(room_config_ac, "destroy");
+    autocomplete_add(room_config_ac, "edit");
     autocomplete_add(room_config_ac, "cancel");
 
     cmd_history_init();
diff --git a/src/command/commands.c b/src/command/commands.c
index 7b49ad61..17eb9157 100644
--- a/src/command/commands.c
+++ b/src/command/commands.c
@@ -1809,7 +1809,9 @@ cmd_room(gchar **args, struct cmd_help_t help)
     }
 
     if ((g_strcmp0(args[1], "accept") != 0) &&
-            (g_strcmp0(args[1], "cancel") != 0)) {
+            (g_strcmp0(args[1], "cancel") != 0) &&
+            (g_strcmp0(args[1], "destroy") != 0) &&
+            (g_strcmp0(args[1], "edit") != 0)) {
         cons_show("Usage: %s", help.usage);
         return TRUE;
     }
@@ -1821,22 +1823,42 @@ cmd_room(gchar **args, struct cmd_help_t help)
     if (ui_index == 10) {
         ui_index = 0;
     }
-    gboolean requires_config = muc_requires_config(room);
-    if (!requires_config) {
-        win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Current room does not require configuration.");
+
+    if (g_strcmp0(args[1], "accept") == 0) {
+        gboolean requires_config = muc_requires_config(room);
+        if (!requires_config) {
+            win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Current room does not require configuration.");
+            return TRUE;
+        } else {
+            iq_confirm_instant_room(room);
+            muc_set_requires_config(room, FALSE);
+            win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Room unlocked.");
+            cons_show("Room unlocked: %s (%d)", room, ui_index);
+            return TRUE;
+        }
+    }
+
+    if (g_strcmp0(args[1], "destroy") == 0) {
+        iq_destroy_instant_room(room);
         return TRUE;
     }
 
-    if (g_strcmp0(args[1], "accept") == 0) {
-        iq_confirm_instant_room(room);
-        muc_set_requires_config(room, FALSE);
-        win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Room unlocked.");
-        cons_show("Room unlocked: %s (%d)", room, ui_index);
+    if (g_strcmp0(args[1], "edit") == 0) {
+        GString *win_title = g_string_new(room);
+        g_string_append(win_title, " config");
+        ProfWin *window = wins_get_by_recipient(win_title->str);
+        g_string_free(win_title, TRUE);
+        if (window != NULL) {
+            int num = wins_get_num(window);
+            ui_switch_win(num);
+        } else {
+            iq_request_room_config_form(room);
+        }
         return TRUE;
     }
 
     if (g_strcmp0(args[1], "cancel") == 0) {
-        iq_destroy_instant_room(room);
+        iq_room_config_cancel(room);
         return TRUE;
     }
 
diff --git a/src/main.c b/src/main.c
index 898cdaf3..e4643d2e 100644
--- a/src/main.c
+++ b/src/main.c
@@ -63,6 +63,7 @@ _init_modules(void)
     message_init_module();
     presence_init_module();
     roster_init_module();
+    form_init_module();
 
     ui_init_module();
     console_init_module();
diff --git a/src/server_events.c b/src/server_events.c
index 408e4e3e..6bc06248 100644
--- a/src/server_events.c
+++ b/src/server_events.c
@@ -464,6 +464,18 @@ handle_room_destroy(const char * const room)
 }
 
 void
+handle_room_configure(const char * const room, DataForm *form)
+{
+    ui_handle_room_configuration(room, form);
+}
+
+void
+handle_room_configuration_form_error(void)
+{
+    cons_show("Error parsing room configuration form.");
+}
+
+void
 handle_room_roster_complete(const char * const room)
 {
     if (muc_room_is_autojoin(room)) {
diff --git a/src/server_events.h b/src/server_events.h
index aaad9923..054d39e4 100644
--- a/src/server_events.h
+++ b/src/server_events.h
@@ -96,5 +96,7 @@ void handle_presence_error(const char *from, const char * const type,
 void handle_xmpp_stanza(const char * const msg);
 void handle_ping_result(const char * const from, int millis);
 void handle_ping_error_result(const char * const from, const char * const error);
+void handle_room_configure(const char * const room, DataForm *form);
+void handle_room_configuration_form_error(void);
 
 #endif
diff --git a/src/ui/core.c b/src/ui/core.c
index 4a7bb365..4f34a210 100644
--- a/src/ui/core.c
+++ b/src/ui/core.c
@@ -1601,7 +1601,8 @@ _ui_room_requires_config(const char * const room_jid)
             ui_index = 0;
         }
 
-        win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "", "Room requires configuration, use '/room config accept' or '/room config cancel'");
+        win_save_vprint(window, '!', NULL, 0, COLOUR_ROOMINFO, "",
+            "Room requires configuration, use '/room config accept' or '/room config destroy'");
 
         // currently in groupchat window
         if (wins_is_current(window)) {
@@ -1870,6 +1871,116 @@ _ui_draw_term_title(void)
 }
 
 static void
+_ui_handle_room_configuration(const char * const room, DataForm *form)
+{
+    GString *title = g_string_new(room);
+    g_string_append(title, " config");
+    ProfWin *window = wins_new(title->str, WIN_MUC_CONFIG);
+    g_string_free(title, TRUE);
+    int num = wins_get_num(window);
+    ui_switch_win(num);
+
+    if (form->title != NULL) {
+        win_save_print(window, '-', NULL, 0, 0, "", form->title);
+    } else {
+        win_save_vprint(window, '-', NULL, 0, 0, "", "Configuration for room %s.", room);
+    }
+    win_save_print(window, '-', NULL, 0, 0, "", "");
+
+    if (form->instructions != NULL) {
+        win_save_vprint(window, '-', NULL, 0, 0, "", "Instructions:");
+        win_save_print(window, '-', NULL, 0, 0, "", form->instructions);
+        win_save_print(window, '-', NULL, 0, 0, "", "");
+    }
+
+    GSList *fields = form->fields;
+    GSList *curr_field = fields;
+    while (curr_field != NULL) {
+        FormField *field = curr_field->data;
+
+        if (g_strcmp0(field->type, "hidden") != 0) {
+            if (field->required) {
+                win_save_vprint(window, '-', NULL, NO_EOL, 0, "", "%s (%s) Required: ", field->label, field->var);
+            } else {
+                win_save_vprint(window, '-', NULL, NO_EOL, 0, "", "%s (%s): ", field->label, field->var);
+            }
+/*
+TODO add command to get help for a field
+            if (field->description != NULL) {
+                win_save_print(window, '-', NULL, 0, 0, "", field->description);
+            }
+*/
+
+            GSList *values = field->values;
+            GSList *curr_value = values;
+            if (g_strcmp0(field->type, "text-single") == 0) {
+                if (curr_value != NULL) {
+                    char *value = curr_value->data;
+                    if (value != NULL) {
+                        if (g_strcmp0(field->var, "muc#roomconfig_roomsecret") == 0) {
+                            win_save_print(window, '-', NULL, NO_DATE | NO_EOL, COLOUR_ONLINE, "", "[hidden]");
+                        } else {
+                            win_save_print(window, '-', NULL, NO_DATE | NO_EOL, COLOUR_ONLINE, "", value);
+                        }
+                    }
+                }
+                win_save_newline(window);
+            }
+            if (g_strcmp0(field->type, "text-private") == 0) {
+                if (curr_value != NULL) {
+                    char *value = curr_value->data;
+                    if (value != NULL) {
+                        win_save_print(window, '-', NULL, NO_DATE | NO_EOL, COLOUR_ONLINE, "", "[hidden]");
+                    }
+                }
+                win_save_newline(window);
+            }
+            if (g_strcmp0(field->type, "boolean") == 0) {
+                if (curr_value == NULL) {
+                    win_save_print(window, '-', NULL, NO_DATE, COLOUR_OFFLINE, "", "FALSE");
+                } else {
+                    char *value = curr_value->data;
+                    if (value == NULL) {
+                        win_save_print(window, '-', NULL, NO_DATE, COLOUR_OFFLINE, "", "FALSE");
+                    } else {
+                        if (g_strcmp0(value, "0") == 0) {
+                            win_save_print(window, '-', NULL, NO_DATE, COLOUR_OFFLINE, "", "FALSE");
+                        } else {
+                            win_save_print(window, '-', NULL, NO_DATE, COLOUR_ONLINE, "", "TRUE");
+                        }
+                    }
+                }
+            }
+            if (g_strcmp0(field->type, "list-single") == 0) {
+                if (curr_value != NULL) {
+                    win_save_newline(window);
+                    char *value = curr_value->data;
+                    GSList *options = field->options;
+                    GSList *curr_option = options;
+                    while (curr_option != NULL) {
+                        FormOption *option = curr_option->data;
+                        if (g_strcmp0(option->value, value) == 0) {
+                            win_save_vprint(window, '-', NULL, 0, COLOUR_ONLINE, "", "  %s (%s)", option->label, option->value);
+                        } else {
+                            win_save_vprint(window, '-', NULL, 0, 0, "", "  %s (%s)", option->label, option->value);
+                        }
+                        curr_option = g_slist_next(curr_option);
+                    }
+                }
+            }
+            if (g_strcmp0(field->type, "list-multi") == 0) {
+            }
+            if (g_strcmp0(field->type, "jid-multi") == 0) {
+            }
+        }
+
+        curr_field = g_slist_next(curr_field);
+    }
+
+    form_destroy(form);
+}
+
+static void
 _win_handle_switch(const wint_t * const ch)
 {
     if (*ch == KEY_F(1)) {
@@ -2106,4 +2217,5 @@ ui_init_module(void)
     ui_update = _ui_update;
     ui_room_requires_config = _ui_room_requires_config;
     ui_room_destroyed = _ui_room_destroyed;
+    ui_handle_room_configuration = _ui_handle_room_configuration;
 }
diff --git a/src/ui/ui.h b/src/ui/ui.h
index 7e8967c0..cb1a92ee 100644
--- a/src/ui/ui.h
+++ b/src/ui/ui.h
@@ -160,6 +160,7 @@ void (*ui_handle_recipient_error)(const char * const recipient, const char * con
 void (*ui_handle_error)(const char * const err_msg);
 void (*ui_clear_win_title)(void);
 void (*ui_handle_room_join_error)(const char * const room, const char * const err);
+void (*ui_handle_room_configuration)(const char * const room, DataForm *form);
 
 // contact status functions
 void (*ui_status_room)(const char * const contact);
diff --git a/src/ui/window.h b/src/ui/window.h
index ed0c64b4..5a72f18f 100644
--- a/src/ui/window.h
+++ b/src/ui/window.h
@@ -58,6 +58,7 @@ typedef enum {
     WIN_CONSOLE,
     WIN_CHAT,
     WIN_MUC,
+    WIN_MUC_CONFIG,
     WIN_PRIVATE,
     WIN_DUCK,
     WIN_XML
diff --git a/src/ui/windows.c b/src/ui/windows.c
index 3d835a0e..5156684b 100644
--- a/src/ui/windows.c
+++ b/src/ui/windows.c
@@ -385,7 +385,11 @@ wins_get_prune_recipients(void)
 
     while (curr != NULL) {
         ProfWin *window = curr->data;
-        if (window->unread == 0 && window->type != WIN_MUC && window->type != WIN_CONSOLE) {
+        if (window->unread == 0 &&
+                window->type != WIN_MUC &&
+                window->type != WIN_MUC_CONFIG &&
+                window->type != WIN_XML &&
+                window->type != WIN_CONSOLE) {
             result = g_slist_append(result, window->from);
         }
         curr = g_list_next(curr);
@@ -539,6 +543,7 @@ wins_create_summary(void)
         GString *chat_string;
         GString *priv_string;
         GString *muc_string;
+        GString *muc_config_string;
         GString *duck_string;
         GString *xml_string;
 
@@ -606,6 +611,14 @@ wins_create_summary(void)
 
                 break;
 
+            case WIN_MUC_CONFIG:
+                muc_config_string = g_string_new("");
+                g_string_printf(muc_config_string, "%d: %s", ui_index, window->from);
+                result = g_slist_append(result, strdup(muc_config_string->str));
+                g_string_free(muc_config_string, TRUE);
+
+                break;
+
             case WIN_DUCK:
                 duck_string = g_string_new("");
                 g_string_printf(duck_string, "%d: DuckDuckGo search", ui_index);
diff --git a/src/xmpp/capabilities.c b/src/xmpp/capabilities.c
index 2fb9f065..942d0697 100644
--- a/src/xmpp/capabilities.c
+++ b/src/xmpp/capabilities.c
@@ -47,6 +47,7 @@
 #include "common.h"
 #include "xmpp/xmpp.h"
 #include "xmpp/stanza.h"
+#include "xmpp/form.h"
 
 static GHashTable *capabilities;
 
@@ -137,7 +138,7 @@ caps_create_sha1_str(xmpp_stanza_t * const query)
     GSList *form_names = NULL;
     DataForm *form = NULL;
     FormField *field = NULL;
-    GHashTable *forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)stanza_destroy_form);
+    GHashTable *forms = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, (GDestroyNotify)form_destroy);
 
     GString *s = g_string_new("");
 
@@ -170,9 +171,10 @@ caps_create_sha1_str(xmpp_stanza_t * const query)
             features = g_slist_insert_sorted(features, g_strdup(feature_str), (GCompareFunc)strcmp);
         } else if (g_strcmp0(xmpp_stanza_get_name(child), STANZA_NAME_X) == 0) {
             if (strcmp(xmpp_stanza_get_ns(child), STANZA_NS_DATA) == 0) {
-                form = stanza_create_form(child);
-                form_names = g_slist_insert_sorted(form_names, g_strdup(form->form_type), (GCompareFunc)strcmp);
-                g_hash_table_insert(forms, g_strdup(form->form_type), form);
+                form = form_create(child);
+                char *form_type = form_get_field_by_var(form, "FORM_TYPE");
+                form_names = g_slist_insert_sorted(form_names, g_strdup(form_type), (GCompareFunc)strcmp);
+                g_hash_table_insert(forms, g_strdup(form_type), form);
             }
         }
         child = xmpp_stanza_get_next(child);
@@ -194,7 +196,8 @@ caps_create_sha1_str(xmpp_stanza_t * const query)
     curr = form_names;
     while (curr != NULL) {
         form = g_hash_table_lookup(forms, curr->data);
-        g_string_append(s, form->form_type);
+        char *form_type = form_get_field_by_var(form, "FORM_TYPE");
+        g_string_append(s, form_type);
         g_string_append(s, "<");
 
         GSList *curr_field = form->fields;
diff --git a/src/xmpp/form.c b/src/xmpp/form.c
new file mode 100644
index 00000000..002f366c
--- /dev/null
+++ b/src/xmpp/form.c
@@ -0,0 +1,258 @@
+/*
+ * form.c
+ *
+ * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity 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.
+ *
+ * Profanity 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 Profanity.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#include <string.h>
+#include <stdlib.h>
+
+#include <strophe.h>
+#include <glib.h>
+
+#include "log.h"
+#include "xmpp/xmpp.h"
+#include "xmpp/stanza.h"
+#include "xmpp/connection.h"
+
+static gboolean
+_is_valid_form_element(xmpp_stanza_t *stanza)
+{
+    char *name = xmpp_stanza_get_name(stanza);
+    if (g_strcmp0(name, STANZA_NAME_X) != 0) {
+        log_error("Error parsing form, root element not <x/>.");
+        return FALSE;
+    }
+
+    char *ns = xmpp_stanza_get_ns(stanza);
+    if (g_strcmp0(ns, STANZA_NS_DATA) != 0) {
+        log_error("Error parsing form, namespace not %s.", STANZA_NS_DATA);
+        return FALSE;
+    }
+
+    char *type = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_TYPE);
+    if ((g_strcmp0(type, "form") != 0) &&
+            (g_strcmp0(type, "submit") != 0) &&
+            (g_strcmp0(type, "cancel") != 0) &&
+            (g_strcmp0(type, "result") != 0)) {
+        log_error("Error parsing form, unknown type.");
+        return FALSE;
+    }
+
+    return TRUE;
+}
+
+static DataForm *
+_form_new(void)
+{
+    DataForm *form = malloc(sizeof(DataForm));
+    form->type = NULL;
+    form->title = NULL;
+    form->instructions = NULL;
+    form->fields = NULL;
+
+    return form;
+}
+
+static FormField *
+_field_new(void)
+{
+    FormField *field = malloc(sizeof(FormField));
+    field->label = NULL;
+    field->type = NULL;
+    field->var = NULL;
+    field->description = NULL;
+    field->required = FALSE;
+    field->values = NULL;
+    field->options = NULL;
+
+    return field;
+}
+
+static char *
+_get_property(xmpp_stanza_t * const stanza, const char * const property)
+{
+    char *result = NULL;
+    xmpp_ctx_t *ctx = connection_get_ctx();
+
+    xmpp_stanza_t *child = xmpp_stanza_get_child_by_name(stanza, property);
+    if (child != NULL) {
+        char *child_text = xmpp_stanza_get_text(child);
+        if (child_text != NULL) {
+            result = strdup(child_text);
+            xmpp_free(ctx, child_text);
+        }
+    }
+
+    return result;
+}
+
+static char *
+_get_attr(xmpp_stanza_t * const stanza, const char * const attr)
+{
+    char *result = xmpp_stanza_get_attribute(stanza, attr);
+    if (result != NULL) {
+        return strdup(result);
+    } else {
+        return NULL;
+    }
+}
+
+static gboolean
+_is_required(xmpp_stanza_t * const stanza)
+{
+    xmpp_stanza_t *child = xmpp_stanza_get_child_by_name(stanza, "required");
+    if (child != NULL) {
+        return TRUE;
+    } else {
+        return FALSE;
+    }
+}
+
+DataForm *
+form_create(xmpp_stanza_t * const form_stanza)
+{
+    xmpp_ctx_t *ctx = connection_get_ctx();
+
+    if (!_is_valid_form_element(form_stanza)) {
+        return NULL;
+    }
+
+    DataForm *form = _form_new();
+    form->type = _get_attr(form_stanza, "type");
+    form->title = _get_property(form_stanza, "title");
+    form->instructions = _get_property(form_stanza, "instructions");
+
+    // get fields
+    xmpp_stanza_t *form_child = xmpp_stanza_get_children(form_stanza);
+    while (form_child != NULL) {
+        char *child_name = xmpp_stanza_get_name(form_child);
+        if (g_strcmp0(child_name, "field") == 0) {
+            xmpp_stanza_t *field_stanza = form_child;
+
+            FormField *field = _field_new();
+            field->label = _get_attr(field_stanza, "label");
+            field->type = _get_attr(field_stanza, "type");
+            field->var = _get_attr(field_stanza, "var");
+            field->description = _get_property(field_stanza, "desc");
+            field->required = _is_required(field_stanza);
+
+            // handle repeated field children
+            xmpp_stanza_t *field_child = xmpp_stanza_get_children(field_stanza);
+            while (field_child != NULL) {
+                child_name = xmpp_stanza_get_name(field_child);
+
+                // handle values
+                if (g_strcmp0(child_name, "value") == 0) {
+                    char *value = xmpp_stanza_get_text(field_child);
+                    if (value != NULL) {
+                        field->values = g_slist_append(field->values, strdup(value));
+                        xmpp_free(ctx, value);
+                    }
+
+                // handle options
+                } else if (g_strcmp0(child_name, "option") == 0) {
+                    FormOption *option = malloc(sizeof(FormOption));
+                    option->label = _get_attr(field_child, "label");
+                    option->value = _get_property(field_child, "value");
+
+                    field->options = g_slist_append(field->options, option);
+                }
+
+                field_child = xmpp_stanza_get_next(field_child);
+            }
+
+            form->fields = g_slist_append(form->fields, field);
+        }
+
+        form_child = xmpp_stanza_get_next(form_child);
+    }
+
+    return form;
+}
+
+static void
+_free_option(FormOption *option)
+{
+    if (option != NULL) {
+        free(option->label);
+        free(option->value);
+        free(option);
+    }
+}
+
+static void
+_free_field(FormField *field)
+{
+    if (field != NULL) {
+        free(field->label);
+        free(field->type);
+        free(field->var);
+        free(field->description);
+        g_slist_free_full(field->values, free);
+        g_slist_free_full(field->options, (GDestroyNotify)_free_option);
+        free(field);
+    }
+}
+
+static void
+_form_destroy(DataForm *form)
+{
+    if (form != NULL) {
+        free(form->type);
+        free(form->title);
+        free(form->instructions);
+        g_slist_free_full(form->fields, (GDestroyNotify)_free_field);
+        free(form);
+    }
+}
+
+static char *
+_form_get_field_by_var(DataForm *form, const char * const var)
+{
+    GSList *curr = form->fields;
+    while (curr != NULL) {
+        FormField *field = curr->data;
+        if (g_strcmp0(field->var, var) == 0) {
+            return field->values->data;
+        }
+        curr = g_slist_next(curr);
+    }
+
+    return NULL;
+}
+
+void
+form_init_module(void)
+{
+    form_destroy = _form_destroy;
+    form_get_field_by_var = _form_get_field_by_var;
+}
diff --git a/src/xmpp/form.h b/src/xmpp/form.h
new file mode 100644
index 00000000..b0c32675
--- /dev/null
+++ b/src/xmpp/form.h
@@ -0,0 +1,40 @@
+/*
+ * form.h
+ *
+ * Copyright (C) 2012 - 2014 James Booth <boothj5@gmail.com>
+ *
+ * This file is part of Profanity.
+ *
+ * Profanity 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.
+ *
+ * Profanity 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 Profanity.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ * In addition, as a special exception, the copyright holders give permission to
+ * link the code of portions of this program with the OpenSSL library under
+ * certain conditions as described in each individual source file, and
+ * distribute linked combinations including the two.
+ *
+ * You must obey the GNU General Public License in all respects for all of the
+ * code used other than OpenSSL. If you modify file(s) with this exception, you
+ * may extend this exception to your version of the file(s), but you are not
+ * obligated to do so. If you do not wish to do so, delete this exception
+ * statement from your version. If you delete this exception statement from all
+ * source files in the program, then also delete it here.
+ *
+ */
+
+#ifndef FORM_H
+#define FROM_H
+
+DataForm* form_create(xmpp_stanza_t * const stanza);
+
+#endif
diff --git a/src/xmpp/iq.c b/src/xmpp/iq.c
index 2224173e..9d72207e 100644
--- a/src/xmpp/iq.c
+++ b/src/xmpp/iq.c
@@ -52,6 +52,7 @@
 #include "xmpp/capabilities.h"
 #include "xmpp/connection.h"
 #include "xmpp/stanza.h"
+#include "xmpp/form.h"
 #include "roster_list.h"
 #include "xmpp/xmpp.h"
 
@@ -75,6 +76,8 @@ static int _disco_items_get_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
 static int _destroy_room_result_handler(xmpp_conn_t * const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
+static int _room_config_handler(xmpp_conn_t * const conn,
+    xmpp_stanza_t * const stanza, void * const userdata);
 static int _manual_pong_handler(xmpp_conn_t *const conn,
     xmpp_stanza_t * const stanza, void * const userdata);
 static int _ping_timed_handler(xmpp_conn_t * const conn,
@@ -99,8 +102,6 @@ iq_add_handlers(void)
 
     HANDLE(STANZA_NS_PING,      STANZA_TYPE_GET,    _ping_get_handler);
 
-    HANDLE(NULL,                STANZA_TYPE_RESULT, _destroy_room_result_handler);
-
     if (prefs_get_autoping() != 0) {
         int millis = prefs_get_autoping() * 1000;
         xmpp_timed_handler_add(conn, _ping_timed_handler, millis, ctx);
@@ -189,6 +190,30 @@ _iq_destroy_instant_room(const char * const room_jid)
 }
 
 static void
+_iq_request_room_config_form(const char * const room_jid)
+{
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    xmpp_stanza_t *iq = stanza_create_room_config_request_iq(ctx, room_jid);
+
+    char *id = xmpp_stanza_get_id(iq);
+    xmpp_id_handler_add(conn, _room_config_handler, id, NULL);
+
+    xmpp_send(conn, iq);
+    xmpp_stanza_release(iq);
+}
+
+static void
+_iq_room_config_cancel(const char * const room_jid)
+{
+    xmpp_conn_t * const conn = connection_get_conn();
+    xmpp_ctx_t * const ctx = connection_get_ctx();
+    xmpp_stanza_t *iq = stanza_create_room_config_cancel_iq(ctx, room_jid);
+    xmpp_send(conn, iq);
+    xmpp_stanza_release(iq);
+}
+
+static void
 _iq_send_ping(const char * const target)
 {
     xmpp_conn_t * const conn = connection_get_conn();
@@ -565,6 +590,52 @@ _destroy_room_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const sta
     return 0;
 }
 
+static int
+_room_config_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanza,
+    void * const userdata)
+{
+    // TODO handle errors
+    const char *id = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_ID);
+
+    if (id != NULL) {
+        log_debug("IQ room config handler fired, id: %s.", id);
+    } else {
+        log_debug("IQ room config handler fired.");
+    }
+
+    const char *from = xmpp_stanza_get_attribute(stanza, STANZA_ATTR_FROM);
+    if (from == NULL) {
+        log_error("No from attribute for IQ destroy room result");
+    } else {
+        // get form
+        xmpp_stanza_t *query = xmpp_stanza_get_child_by_name(stanza, STANZA_NAME_QUERY);
+        if (query == NULL) {
+            log_error("No query element found parsing room config response");
+            handle_room_configuration_form_error();
+            return 0;
+        }
+
+        xmpp_stanza_t *x = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA);
+        if (x == NULL) {
+            log_error("No x element found with %s namespace parsing room config response", STANZA_NS_DATA);
+            handle_room_configuration_form_error();
+            return 0;
+        }
+
+        char *type = xmpp_stanza_get_attribute(x, STANZA_ATTR_TYPE);
+        if (g_strcmp0(type, "form") != 0) {
+            log_error("x element not of type 'form' parsing room config response");
+            handle_room_configuration_form_error();
+            return 0;
+        }
+
+        DataForm *form = form_create(x);
+        handle_room_configure(from, form);
+    }
+
+    return 0;
+}
+
 static void
 _identity_destroy(DiscoIdentity *identity)
 {
@@ -711,10 +782,11 @@ _disco_info_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanz
 
         xmpp_stanza_t *softwareinfo = xmpp_stanza_get_child_by_ns(query, STANZA_NS_DATA);
         if (softwareinfo != NULL) {
-            DataForm *form = stanza_create_form(softwareinfo);
+            DataForm *form = form_create(softwareinfo);
             FormField *formField = NULL;
 
-            if (g_strcmp0(form->form_type, STANZA_DATAFORM_SOFTWARE) == 0) {
+            char *form_type = form_get_field_by_var(form, "FORM_TYPE");
+            if (g_strcmp0(form_type, STANZA_DATAFORM_SOFTWARE) == 0) {
                 GSList *field = form->fields;
                 while (field != NULL) {
                     formField = field->data;
@@ -733,7 +805,7 @@ _disco_info_result_handler(xmpp_conn_t * const conn, xmpp_stanza_t * const stanz
                 }
             }
 
-            stanza_destroy_form(form);
+            form_destroy(form);
         }
 
         xmpp_stanza_t *child = xmpp_stanza_get_children(query);
@@ -814,4 +886,6 @@ iq_init_module(void)
     iq_confirm_instant_room = _iq_confirm_instant_room;
     iq_destroy_instant_room = _iq_destroy_instant_room;
     iq_send_ping = _iq_send_ping;
+    iq_request_room_config_form = _iq_request_room_config_form;
+    iq_room_config_cancel = _iq_room_config_cancel;
 }
diff --git a/src/xmpp/stanza.c b/src/xmpp/stanza.c
index 654ba121..519862f7 100644
--- a/src/xmpp/stanza.c
+++ b/src/xmpp/stanza.c
@@ -46,8 +46,6 @@
 
 #include "muc.h"
 
-static int _field_compare(FormField *f1, FormField *f2);
-
 #if 0
 xmpp_stanza_t *
 stanza_create_bookmarks_pubsub_request(xmpp_ctx_t *ctx)
@@ -430,7 +428,7 @@ stanza_create_instant_room_request_iq(xmpp_ctx_t *ctx, const char * const room_j
     xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
     xmpp_stanza_set_type(iq, STANZA_TYPE_SET);
     xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, room_jid);
-    char *id = create_unique_id("leave");
+    char *id = create_unique_id("room");
     xmpp_stanza_set_id(iq, id);
     free(id);
 
@@ -459,7 +457,7 @@ stanza_create_instant_room_destroy_iq(xmpp_ctx_t *ctx, const char * const room_j
     xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
     xmpp_stanza_set_type(iq, STANZA_TYPE_SET);
     xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, room_jid);
-    char *id = create_unique_id("leave");
+    char *id = create_unique_id("room");
     xmpp_stanza_set_id(iq, id);
     free(id);
 
@@ -480,6 +478,56 @@ stanza_create_instant_room_destroy_iq(xmpp_ctx_t *ctx, const char * const room_j
 }
 
 xmpp_stanza_t *
+stanza_create_room_config_request_iq(xmpp_ctx_t *ctx, const char * const room_jid)
+{
+    xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
+    xmpp_stanza_set_type(iq, STANZA_TYPE_GET);
+    xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, room_jid);
+    char *id = create_unique_id("room");
+    xmpp_stanza_set_id(iq, id);
+    free(id);
+
+    xmpp_stanza_t *query = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(query, STANZA_NAME_QUERY);
+    xmpp_stanza_set_ns(query, STANZA_NS_MUC_OWNER);
+
+    xmpp_stanza_add_child(iq, query);
+    xmpp_stanza_release(query);
+
+    return iq;
+}
+
+xmpp_stanza_t *
+stanza_create_room_config_cancel_iq(xmpp_ctx_t *ctx, const char * const room_jid)
+{
+    xmpp_stanza_t *iq = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(iq, STANZA_NAME_IQ);
+    xmpp_stanza_set_type(iq, STANZA_TYPE_SET);
+    xmpp_stanza_set_attribute(iq, STANZA_ATTR_TO, room_jid);
+    char *id = create_unique_id("room");
+    xmpp_stanza_set_id(iq, id);
+    free(id);
+
+    xmpp_stanza_t *query = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(query, STANZA_NAME_QUERY);
+    xmpp_stanza_set_ns(query, STANZA_NS_MUC_OWNER);
+
+    xmpp_stanza_t *x = xmpp_stanza_new(ctx);
+    xmpp_stanza_set_name(x, STANZA_NAME_X);
+    xmpp_stanza_set_type(x, "cancel");
+    xmpp_stanza_set_ns(x, STANZA_NS_DATA);
+
+    xmpp_stanza_add_child(query, x);
+    xmpp_stanza_release(x);
+
+    xmpp_stanza_add_child(iq, query);
+    xmpp_stanza_release(query);
+
+    return iq;
+}
+
+xmpp_stanza_t *
 stanza_create_presence(xmpp_ctx_t * const ctx)
 {
     xmpp_stanza_t *presence = xmpp_stanza_new(ctx);
@@ -1023,79 +1071,6 @@ stanza_get_error_message(xmpp_stanza_t *stanza)
     return strdup("unknown");
 }
 
-DataForm *
-stanza_create_form(xmpp_stanza_t * const stanza)
-{
-    DataForm *result = NULL;
-    xmpp_ctx_t *ctx = connection_get_ctx();
-
-    xmpp_stanza_t *child = xmpp_stanza_get_children(stanza);
-
-    if (child != NULL) {
-        result = malloc(sizeof(DataForm));
-        result->form_type = NULL;
-        result->fields = NULL;
-    }
-
-    //handle fields
-    while (child != NULL) {
-        char *var = xmpp_stanza_get_attribute(child, "var");
-
-        // handle FORM_TYPE
-        if (g_strcmp0(var, "FORM_TYPE") == 0) {
-            xmpp_stanza_t *value = xmpp_stanza_get_child_by_name(child, "value");
-            char *value_text = xmpp_stanza_get_text(value);
-            result->form_type = strdup(value_text);
-            xmpp_free(ctx, value_text);
-
-        // handle regular fields
-        } else {
-            FormField *field = malloc(sizeof(FormField));
-            field->var = strdup(var);
-            field->values = NULL;
-            xmpp_stanza_t *value = xmpp_stanza_get_children(child);
-
-            // handle values
-            while (value != NULL) {
-                char *text = xmpp_stanza_get_text(value);
-                if (text != NULL) {
-                    field->values = g_slist_insert_sorted(field->values, strdup(text), (GCompareFunc)strcmp);
-                    xmpp_free(ctx, text);
-                }
-                value = xmpp_stanza_get_next(value);
-            }
-
-            result->fields = g_slist_insert_sorted(result->fields, field, (GCompareFunc)_field_compare);
-        }
-
-        child = xmpp_stanza_get_next(child);
-    }
-
-    return result;
-}
-
-void
-stanza_destroy_form(DataForm *form)
-{
-    if (form != NULL) {
-        if (form->fields != NULL) {
-            GSList *curr_field = form->fields;
-            while (curr_field != NULL) {
-                FormField *field = curr_field->data;
-                free(field->var);
-                if ((field->values) != NULL) {
-                    g_slist_free_full(field->values, free);
-                }
-                curr_field = curr_field->next;
-            }
-            g_slist_free_full(form->fields, free);
-        }
-
-        free(form->form_type);
-        free(form);
-    }
-}
-
 void
 stanza_attach_priority(xmpp_ctx_t * const ctx, xmpp_stanza_t * const presence,
     const int pri)
@@ -1199,9 +1174,3 @@ stanza_get_presence_string_from_type(resource_presence_t presence_type)
             return NULL;
     }
 }
-
-static int
-_field_compare(FormField *f1, FormField *f2)
-{
-    return strcmp(f1->var, f2->var);
-}
diff --git a/src/xmpp/stanza.h b/src/xmpp/stanza.h
index 3d925787..b13c2960 100644
--- a/src/xmpp/stanza.h
+++ b/src/xmpp/stanza.h
@@ -36,6 +36,7 @@
 #define XMPP_STANZA_H
 
 #include <strophe.h>
+#include <xmpp/xmpp.h>
 
 #define STANZA_NAME_ACTIVE "active"
 #define STANZA_NAME_INACTIVE "inactive"
@@ -154,16 +155,6 @@
 
 #define STANZA_DATAFORM_SOFTWARE "urn:xmpp:dataforms:softwareinfo"
 
-typedef struct form_field_t {
-    char *var;
-    GSList *values;
-} FormField;
-
-typedef struct data_form_t {
-    char *form_type;
-    GSList *fields;
-} DataForm;
-
 xmpp_stanza_t* stanza_create_bookmarks_storage_request(xmpp_ctx_t *ctx);
 
 xmpp_stanza_t* stanza_create_chat_state(xmpp_ctx_t *ctx,
@@ -207,6 +198,10 @@ xmpp_stanza_t* stanza_create_instant_room_request_iq(xmpp_ctx_t *ctx,
     const char * const room_jid);
 xmpp_stanza_t* stanza_create_instant_room_destroy_iq(xmpp_ctx_t *ctx,
     const char * const room_jid);
+xmpp_stanza_t* stanza_create_room_config_request_iq(xmpp_ctx_t *ctx,
+    const char * const room_jid);
+xmpp_stanza_t* stanza_create_room_config_cancel_iq(xmpp_ctx_t *ctx,
+    const char * const room_jid);
 
 int stanza_get_idle_time(xmpp_stanza_t * const stanza);
 char * stanza_get_caps_str(xmpp_stanza_t * const stanza);
diff --git a/src/xmpp/xmpp.h b/src/xmpp/xmpp.h
index 78703230..6510a50e 100644
--- a/src/xmpp/xmpp.h
+++ b/src/xmpp/xmpp.h
@@ -86,6 +86,28 @@ typedef struct disco_identity_t {
     char *category;
 } DiscoIdentity;
 
+typedef struct form_option_t {
+    char *label;
+    char *value;
+} FormOption;
+
+typedef struct form_field_t {
+    char *label;
+    char *type;
+    char *var;
+    char *description;
+    gboolean required;
+    GSList *values;
+    GSList *options;
+} FormField;
+
+typedef struct data_form_t {
+    char *type;
+    char *title;
+    char *instructions;
+    GSList *fields;
+} DataForm;
+
 void jabber_init_module(void);
 void bookmark_init_module(void);
 void capabilities_init_module(void);
@@ -93,6 +115,7 @@ void iq_init_module(void);
 void message_init_module(void);
 void presence_init_module(void);
 void roster_init_module(void);
+void form_init_module(void);
 
 // connection functions
 void (*jabber_init)(const int disable_tls);
@@ -141,6 +164,8 @@ void (*iq_disco_items_request)(gchar *jid);
 void (*iq_set_autoping)(int seconds);
 void (*iq_confirm_instant_room)(const char * const room_jid);
 void (*iq_destroy_instant_room)(const char * const room_jid);
+void (*iq_request_room_config_form)(const char * const room_jid);
+void (*iq_room_config_cancel)(const char * const room_jid);
 void (*iq_send_ping)(const char * const target);
 
 // caps functions
@@ -161,4 +186,7 @@ void (*roster_send_remove_from_group)(const char * const group, PContact contact
 void (*roster_send_add_new)(const char * const barejid, const char * const name);
 void (*roster_send_remove)(const char * const barejid);
 
+void (*form_destroy)(DataForm *form);
+char * (*form_get_field_by_var)(DataForm *form, const char * const var);
+
 #endif