From 9345e5cfa7d34a86c0df75cf3c13253b77aa1bdd Mon Sep 17 00:00:00 2001 From: Dave Peticolas Date: Thu, 8 Mar 2001 00:37:13 +0000 Subject: [PATCH] Bill Gribble's patch. * summary: separate the HTML and HTTP processing functions into different files to facilitate reuse. Modularize much of the gnucash-specific behavior of the HTML code ( and form submission) to use run-time-expandable bahavior tables. Add the gnc-action: mechanism for installing form submission handlers. * src/gnome/gng-gpg.c: initialize gnc-html handler for crypted HTML objects. Remove all mention of GPG from gnc-html.c * src/gnome/gnc-html-actions.c: new file. Add a simple form submission action (action=gnc-action:gnc-info/form?CGI_URL) to test submit and action processing. This is useless ATM. Some of the stuff in the privacy comments is unimplemented yet. * src/gnome/gnc-html-guppi.c: move all Guppi references from gnc-html.c into a separate file, with an initializer for the Guppi tags. * src/gnome/gnc-html.c: get rid of SSL references; all that stuff is now in gnc-http.c. Restructure to use gnc-http instead of ghttp directly. Finish GET and POST default handlers, and add handler lookup/install mechanism for gnc-action: actions. crib urlencoding function from gtkhtml guts. * src/gnome/gnc-http.c: new file. Move HTTP stuff here. Finish POST handling. * src/gnome/top-level.c: add calls to Guppi, GPG, and gnc-html init functions. These calls will eventually go into loadable module startup functions, when we get loadable modules. * src/scm/html-text.scm: Add html-markup/format. (html-markup/format "%a %a %a %a" 1 2 3 4) does what you'd expect, even if the non-format args are html-markup objects. git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@3759 57a11ea4-9604-0410-9ed3-97b8803252fd --- ChangeLog | 37 + src/gnome/Makefile.am | 8 +- src/gnome/gnc-gpg.c | 63 ++ src/gnome/gnc-gpg.h | 11 +- src/gnome/gnc-html-actions.c | 134 ++++ src/gnome/gnc-html-actions.h | 30 + .../{gnc-html-embedded.c => gnc-html-guppi.c} | 117 ++- .../{gnc-html-embedded.h => gnc-html-guppi.h} | 7 +- src/gnome/gnc-html-history.c | 3 +- src/gnome/gnc-html.c | 710 +++++++++--------- src/gnome/gnc-html.h | 54 +- src/gnome/gnc-http.c | 249 ++++++ src/gnome/gnc-http.h | 43 ++ src/gnome/top-level.c | 21 +- src/gnome/window-help.c | 15 +- src/scm/html-text.scm | 44 +- src/scm/report/hello-world.scm | 24 +- 17 files changed, 1142 insertions(+), 428 deletions(-) create mode 100644 src/gnome/gnc-html-actions.c create mode 100644 src/gnome/gnc-html-actions.h rename src/gnome/{gnc-html-embedded.c => gnc-html-guppi.c} (90%) rename src/gnome/{gnc-html-embedded.h => gnc-html-guppi.h} (92%) create mode 100644 src/gnome/gnc-http.c create mode 100644 src/gnome/gnc-http.h diff --git a/ChangeLog b/ChangeLog index 12650019d9..809742a463 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,40 @@ +2001-03-07 Bill Gribble + + * summary: separate the HTML and HTTP processing functions into + different files to facilitate reuse. Modularize much of the + gnucash-specific behavior of the HTML code ( and form + submission) to use run-time-expandable bahavior tables. Add the + gnc-action: mechanism for installing form submission handlers. + + * src/gnome/gng-gpg.c: initialize gnc-html handler for crypted + HTML objects. Remove all mention of GPG from gnc-html.c + + * src/gnome/gnc-html-actions.c: new file. Add a simple form + submission action (action=gnc-action:gnc-info/form?CGI_URL) to + test submit and action processing. This is useless ATM. Some of + the stuff in the privacy comments is unimplemented yet. + + * src/gnome/gnc-html-guppi.c: move all Guppi references from + gnc-html.c into a separate file, with an initializer for + the Guppi tags. + + * src/gnome/gnc-html.c: get rid of SSL references; all that stuff + is now in gnc-http.c. Restructure to use gnc-http instead of + ghttp directly. Finish GET and POST default handlers, and add + handler lookup/install mechanism for gnc-action: actions. crib + urlencoding function from gtkhtml guts. + + * src/gnome/gnc-http.c: new file. Move HTTP stuff here. Finish + POST handling. + + * src/gnome/top-level.c: add calls to Guppi, GPG, and gnc-html + init functions. These calls will eventually go into loadable + module startup functions, when we get loadable modules. + + * src/scm/html-text.scm: Add html-markup/format. + (html-markup/format "%a %a %a %a" 1 2 3 4) does what you'd expect, + even if the non-format args are html-markup objects. + 2001-03-07 Dave Peticolas * src/gnome/top-level.c (gnc_ui_check_events): add timeout diff --git a/src/gnome/Makefile.am b/src/gnome/Makefile.am index 1f6ace2915..6fc3349ea5 100644 --- a/src/gnome/Makefile.am +++ b/src/gnome/Makefile.am @@ -34,9 +34,11 @@ libgncgnome_a_SOURCES = \ gnc-datedelta.c \ gnc-dateedit.c \ gnc-gpg.c \ - gnc-html-embedded.c \ + gnc-html-actions.c \ gnc-html-history.c \ + gnc-html-guppi.c \ gnc-html.c \ + gnc-http.c \ gnc-splash.c \ gtkselect.c \ mainwindow-account-tree.c \ @@ -89,9 +91,11 @@ noinst_HEADERS = \ gnc-datedelta.h \ gnc-dateedit.h \ gnc-gpg.h \ - gnc-html-embedded.h \ + gnc-html-actions.h \ gnc-html-history.h \ + gnc-html-guppi.h \ gnc-html.h \ + gnc-http.h \ gnc-splash.h \ gtkselect.h \ mainwindow-account-tree.h \ diff --git a/src/gnome/gnc-gpg.c b/src/gnome/gnc-gpg.c index c104247c37..88c9d67a80 100644 --- a/src/gnome/gnc-gpg.c +++ b/src/gnome/gnc-gpg.c @@ -23,13 +23,74 @@ #include "config.h" +#if USE_GPG + #include #include #include #include +#include "gnc-html.h" #include "gnc-gpg.h" +static int handle_gpg_html(gnc_html * html, GtkHTMLEmbedded * eb, gpointer d); + +/******************************************************************** + * gnc_gpg_init : called at startup time. adds a handler for crypted + * HTML to gnc-html. + ********************************************************************/ + +void +gnc_gpg_init(void) { + gnc_html_register_object_handler("gnc-crypted-html", handle_gpg_html); +} + +static char * +unescape_newlines(const gchar * in) { + const char * ip = in; + char * retval = g_strdup(in); + char * op = retval; + + for(ip=in; *ip; ip++) { + if((*ip == '\\') && (*(ip+1)=='n')) { + *op = '\012'; + op++; + ip++; + } + else { + *op = *ip; + op++; + } + } + *op = 0; + return retval; +} + +/* we just want to take the data and stuff it into the gnc-html + * widget, blowing away the active streams. crypted-html contains a + * complete HTML page. */ +static int +handle_gpg_html(gnc_html * html, GtkHTMLEmbedded * eb, gpointer data) { + int retval; + char * cryptext = unescape_newlines(eb->data); + char * cleartext = gnc_gpg_decrypt(cryptext, strlen(cryptext)); + GtkHTMLStream * handle; + if(cleartext && cleartext[0]) { + handle = gtk_html_begin(html); + gtk_html_write(html, handle, cleartext, strlen(cleartext)); + gtk_html_end(html, handle, GTK_HTML_STREAM_OK); + retval = TRUE; + } + else { + retval = FALSE; + } + g_free(cleartext); + g_free(cryptext); + + return retval; +} + + static char * gnc_gpg_transform(const gchar * input, gint input_size, char ** gpg_argv) { @@ -206,3 +267,5 @@ gnc_gpg_make_keypair(const gchar * username, gnc_gpg_transform(stdin, strlen(stdin), argv); g_free(stdin); } + +#endif diff --git a/src/gnome/gnc-gpg.h b/src/gnome/gnc-gpg.h index eb53b1fe29..81c3a64981 100644 --- a/src/gnome/gnc-gpg.h +++ b/src/gnome/gnc-gpg.h @@ -26,10 +26,11 @@ #include "config.h" -char * gnc_gpg_encrypt(const gchar * cleartext, int cleartext_size, - const gchar * recipient); -char * gnc_gpg_decrypt(const gchar * cleartext, int cleartext_size); -void gnc_gpg_make_keypair(const gchar * name, const gchar * id, - const gchar * email); +void gnc_gpg_init(void); +char * gnc_gpg_encrypt(const gchar * cleartext, int cleartext_size, + const gchar * recipient); +char * gnc_gpg_decrypt(const gchar * cleartext, int cleartext_size); +void gnc_gpg_make_keypair(const gchar * name, const gchar * id, + const gchar * email); #endif diff --git a/src/gnome/gnc-html-actions.c b/src/gnome/gnc-html-actions.c new file mode 100644 index 0000000000..aa86f89650 --- /dev/null +++ b/src/gnome/gnc-html-actions.c @@ -0,0 +1,134 @@ +/******************************************************************** + * gnc-html-actions.c -- form submission actions * + * Copyright (C) 2000 Bill Gribble * + * * + * This program 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 2 of * + * the License, or (at your option) any later version. * + * * + * This program 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 this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + ********************************************************************/ + + +/******************************************************************** + * THIS IS PRIVACY-SENSITIVE CODE. The actions handled here are run + * when gnucash needs to send HTML form information to a web server + * and can modify the information being sent or send additional + * information. + * + * Don't panic; all that means is that when an HTML form is loaded + * into the Gnucash HTML browser, after the user clicks the "submit" + * button, if the "action" in the form definition looks like + * "gnc-action:ACTION-NAME?ACTION-ARGS", the action-name is used to + * find a handler and the form is submitted by that handler. The + * default action handlers allow Gnucash to send information about + * itself along with a form, to send the form data in a GPG-encrypted + * block, etc. This is useful functionality, but there are real + * privacy concerns. + * + * Users: keep in mind that this is *not* a mechanism for executing + * arbitrary code in your gnucash; the only "actions" that can be + * taken must be compiled into the client, and you can disable them + * globally with a Preferences option. + * + * Developers: Until we have a formally-codified privacy policy, + * please follow these guidelines: + * + * 1. When actions send information that the user has not explicitly + * entered, there should *always* be a dialog or other notification, + * including the extra information to be sent, with an opportunity to + * bail out. + * + * 2. Do not use a accept a complete URI to submit to as an argument + * to actions. This might allow a malicious server to ask the + * gnucash client for private information to be sent to itself, if + * the user happened to go to that site from within the gnucash HTML + * browser and click a form "SUBMIT" button. Submit only to servers + * whose names the local user has specified as trusted servers for + * gnucash-related actions. + ********************************************************************/ + +#include "config.h" +#include +#include + +#include "gnc-html-actions.h" +#include "gnc-html.h" + +static int handle_gnc_info_form_submit(gnc_html * html, const char * method, + const char * action, const char * args, + const char * encoding); + +/******************************************************************** + * gnc_html_actions_init() : register the basic set of gnc-action: + * form submission actions. + ********************************************************************/ + +void +gnc_html_actions_init(void) { + gnc_html_register_action_handler("gnc-info/form", + handle_gnc_info_form_submit); +} + + +/******************************************************************** + * handle_gnc_info_form_submit() : submit the form arguments from + * 'encoding', appending additional arguments describing the gnucash + * client. this is justa test and doesn't submit any real + * information. + ********************************************************************/ + +static int +handle_gnc_info_form_submit(gnc_html * html, const char * method, + const char * action, const char * args, + const char * encoding) { + char * extra_encoding = NULL; + char * new_encoding = NULL; + char * new_action = NULL; + char * msg_1 = gnc_html_encode_string("gnucash version 1.5"); + char * msg_2 = gnc_html_encode_string("Hello, world"); + + if(!method || !action + || strcmp(action, "gnc-info/form")) { + return FALSE; + } + + extra_encoding = g_strconcat("gnc_version=", msg_1, "&" + "gnc_message=", msg_2, NULL); + + if(encoding) { + new_encoding = g_strjoin("&", encoding, extra_encoding, NULL); + } + else { + new_encoding = extra_encoding; + extra_encoding = NULL; + } + + new_action = g_strconcat("http://localhost/", args, NULL); + + if(!strcasecmp(method, "get")) { + gnc_html_generic_get_submit(html, new_action, new_encoding); + } + else { + gnc_html_generic_post_submit(html, new_action, new_encoding); + } + + g_free(extra_encoding); + g_free(new_encoding); + g_free(new_action); + g_free(msg_1); + g_free(msg_2); + + return TRUE; +} diff --git a/src/gnome/gnc-html-actions.h b/src/gnome/gnc-html-actions.h new file mode 100644 index 0000000000..394abee903 --- /dev/null +++ b/src/gnome/gnc-html-actions.h @@ -0,0 +1,30 @@ +/******************************************************************** + * gnc-html-actions.h -- basic form submission actions * + * Copyright (C) 2000 Bill Gribble * + * * + * This program 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 2 of * + * the License, or (at your option) any later version. * + * * + * This program 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 this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * +\********************************************************************/ + +#ifndef __GNC_HTML_ACTIONS_H__ +#define __GNC_HTML_ACTIONS_H__ + +#include "gnc-html.h" + +void gnc_html_actions_init(void); + +#endif diff --git a/src/gnome/gnc-html-embedded.c b/src/gnome/gnc-html-guppi.c similarity index 90% rename from src/gnome/gnc-html-embedded.c rename to src/gnome/gnc-html-guppi.c index cdf92bbe40..f38402d6c3 100644 --- a/src/gnome/gnc-html-embedded.c +++ b/src/gnome/gnc-html-guppi.c @@ -1,5 +1,5 @@ /******************************************************************** - * gnc-html-embedded.c -- embed objects in the html stream. * + * gnc-html-guppi.c -- embed guppi objects in the html stream. * * Copyright (C) 2000 Bill Gribble * * * * This program is free software; you can redistribute it and/or * @@ -22,16 +22,96 @@ #include "config.h" +#ifdef USE_GUPPI + #include #include #include +#include +#include -#ifdef USE_GUPPI #include -#endif +#include "gnc-html.h" +#include "gnc-html-guppi.h" -#include "gnc-html-embedded.h" -#include "mainwindow-account-tree.h" +static int handle_piechart(gnc_html * html, GtkHTMLEmbedded * eb, gpointer d); +static int handle_barchart(gnc_html * html, GtkHTMLEmbedded * eb, gpointer d); +static int handle_scatter(gnc_html * html, GtkHTMLEmbedded * eb, gpointer d); + + +/******************************************************************** + * gnc_html_guppi_init + * add object handlers for guppi objects + ********************************************************************/ + +void +gnc_html_guppi_init(void) { + guppi_tank_init(); + gnc_html_register_object_handler("gnc-guppi-pie", handle_piechart); + gnc_html_register_object_handler("gnc-guppi-bar", handle_barchart); + gnc_html_register_object_handler("gnc-guppi-scatter", handle_scatter); +} + +/* the handlers for pie. bar, and scatter charts */ +static int +handle_piechart(gnc_html * html, GtkHTMLEmbedded * eb, gpointer data) { + GtkWidget * widg = NULL; + int retval; + widg = gnc_html_embedded_piechart(html, eb->width, eb->height, + eb->params); + if(widg) { + gtk_widget_show_all(widg); + gtk_container_add(GTK_CONTAINER(eb), widg); + gtk_widget_set_usize(GTK_WIDGET(eb), eb->width, eb->height); + retval = TRUE; + } + else { + retval = FALSE; + } + return retval; +} + +static int +handle_barchart(gnc_html * html, GtkHTMLEmbedded * eb, gpointer data) { + GtkWidget * widg = NULL; + int retval; + widg = gnc_html_embedded_barchart(html, eb->width, eb->height, + eb->params); + if(widg) { + gtk_widget_show_all(widg); + gtk_container_add(GTK_CONTAINER(eb), widg); + gtk_widget_set_usize(GTK_WIDGET(eb), eb->width, eb->height); + retval = TRUE; + } + else { + retval = FALSE; + } + return retval; +} + +static int +handle_scatter(gnc_html * html, GtkHTMLEmbedded * eb, gpointer data) { + GtkWidget * widg = NULL; + int retval; + widg = gnc_html_embedded_scatter(html, eb->width, eb->height, + eb->params); + if(widg) { + gtk_widget_show_all(widg); + gtk_container_add(GTK_CONTAINER(eb), widg); + gtk_widget_set_usize(GTK_WIDGET(eb), eb->width, eb->height); + retval = TRUE; + } + else { + retval = FALSE; + } + return retval; +} + + +/******************************************************************** + * now we start the actual embedded object creation routines. well, + * after some utilities. + ********************************************************************/ static double * read_doubles(const char * string, int nvalues) { @@ -102,8 +182,6 @@ free_strings(char ** strings, int nstrings) { } -#ifdef USE_GUPPI - struct guppi_chart_data { GtkWidget * widget; GuppiObject * guppiobject; @@ -725,7 +803,6 @@ gnc_html_embedded_scatter(gnc_html * parent, return NULL; } -#endif /* USE_GUPPI */ /******************************************************************** * gnc_html_embedded_account_tree @@ -751,26 +828,4 @@ set_bools(char * indices, gboolean * array, int num) { } -GtkWidget * -gnc_html_embedded_account_tree(gnc_html * parent, - int w, int h, GHashTable * params) { - AccountViewInfo info; - GtkWidget * tree = gnc_mainwin_account_tree_new(); - char * param; - int * fields; - - memset(&info, 0, sizeof(AccountViewInfo)); - - if((param = g_hash_table_lookup(params, "fields"))) { - set_bools(param, &(info.show_field[0]), NUM_ACCOUNT_FIELDS); - } - if((param = g_hash_table_lookup(params, "types"))) { - set_bools(param, &(info.include_type[0]), NUM_ACCOUNT_TYPES); - } - gnc_mainwin_account_tree_set_view_info(GNC_MAINWIN_ACCOUNT_TREE(tree), - info); - - gnc_account_tree_refresh(GNC_ACCOUNT_TREE - (GNC_MAINWIN_ACCOUNT_TREE(tree)->acc_tree)); - return tree; -} +#endif diff --git a/src/gnome/gnc-html-embedded.h b/src/gnome/gnc-html-guppi.h similarity index 92% rename from src/gnome/gnc-html-embedded.h rename to src/gnome/gnc-html-guppi.h index a81d962894..402fea1db2 100644 --- a/src/gnome/gnc-html-embedded.h +++ b/src/gnome/gnc-html-guppi.h @@ -1,5 +1,5 @@ /******************************************************************** - * gnc-html-embedded.h -- embed objects in the html stream * + * gnc-html-guppi.h -- embed objects in the html stream * * Copyright (C) 2000 Bill Gribble * * * * This program is free software; you can redistribute it and/or * @@ -20,12 +20,13 @@ * Boston, MA 02111-1307, USA gnu@gnu.org * \********************************************************************/ -#ifndef __GNC_HTML_EMBEDDED_H__ -#define __GNC_HTML_EMBEDDED_H__ +#ifndef __GNC_HTML_GUPPI_H__ +#define __GNC_HTML_GUPPI_H__ #include #include "gnc-html.h" +void gnc_html_guppi_init(void); GtkWidget * gnc_html_embedded_piechart(gnc_html * parent, gint w, gint h, GHashTable * params); GtkWidget * gnc_html_embedded_barchart(gnc_html * parent, diff --git a/src/gnome/gnc-html-history.c b/src/gnome/gnc-html-history.c index 0de11604f8..a5c74649df 100644 --- a/src/gnome/gnc-html-history.c +++ b/src/gnome/gnc-html-history.c @@ -23,6 +23,7 @@ #include "config.h" #include +#include #include "gnc-html-history.h" @@ -41,7 +42,7 @@ struct _gnc_html_history { ********************************************************************/ gnc_html_history * -gnc_html_history_new() { +gnc_html_history_new(void) { gnc_html_history * hist = g_new0(gnc_html_history, 1); hist->nodes = NULL; hist->current_node = NULL; diff --git a/src/gnome/gnc-html.c b/src/gnome/gnc-html.c index 11189c8640..814d81a2f7 100644 --- a/src/gnome/gnc-html.c +++ b/src/gnome/gnc-html.c @@ -32,16 +32,6 @@ #include #include -#ifdef HAVE_OPENSSL -#include -#include -#include -#include -#include -#include -#endif - -#include #include #include #ifdef USE_GUPPI @@ -64,29 +54,30 @@ #include "gnc-engine-util.h" #include "gnc-gpg.h" #include "gnc-html.h" +#include "gnc-html-actions.h" +#include "gnc-http.h" #include "gnc-html-history.h" -#include "gnc-html-embedded.h" #include "query-user.h" #include "window-help.h" #include "window-report.h" struct _gnc_html { - GtkWidget * container; - GtkWidget * html; + GtkWidget * container; /* parent of the gtkhtml widget */ + GtkWidget * html; /* gtkhtml widget itself */ + gchar * current_link; /* link under mouse pointer */ - gchar * current_link; /* link under mouse pointer */ + URLType base_type; /* base of URL (path - filename) */ + gchar * base_location; - URLType base_type; /* base of URL (path - filename) */ - gchar * base_location; + gnc_http * http; /* handles HTTP requests */ + GHashTable * request_info; /* hash uri to GList of GtkHTMLStream * */ /* callbacks */ - GncHTMLUrltypeCB urltype_cb; + GncHTMLUrltypeCB urltype_cb; /* is this type OK for this instance? */ GncHTMLLoadCB load_cb; GncHTMLFlyoverCB flyover_cb; GncHTMLButtonCB button_cb; - - GList * requests; /* outstanding ghttp requests */ - + gpointer flyover_cb_data; gpointer load_cb_data; gpointer button_cb_data; @@ -94,15 +85,18 @@ struct _gnc_html { struct _gnc_html_history * history; }; -struct request_info { - gchar * uri; - ghttp_request * request; - GtkHTMLStream * handle; -}; -/* This static indicates the debugging module that this .o belongs to. */ +/* indicates the debugging module that this .o belongs to. */ static short module = MOD_HTML; +/* hashes an HTML classid to a handler function */ +static GHashTable * gnc_html_object_handlers = NULL; + +/* hashes an action name from a FORM definition to a handler function. + *
+ * action-args is what gets passed to the handler. */ +static GHashTable * gnc_html_action_handlers = NULL; + static char error_404[] = "

Not found

The specified URL could not be loaded."; @@ -170,6 +164,9 @@ gnc_html_parse_url(gnc_html * html, const gchar * url, else if(!strcmp(protocol, "https")) { retval = URL_TYPE_SECURE; } + else if(!strcmp(protocol, "gnc-action")) { + retval = URL_TYPE_ACTION; + } else if(!strcmp(protocol, "gnc-register")) { retval = URL_TYPE_REGISTER; } @@ -326,152 +323,100 @@ rebuild_url(URLType type, const gchar * location, const gchar * label) { } } -static guint ghttp_callback_tag = 0; -static int ghttp_callback_enabled = FALSE; +/************************************************************ + * gnc_html_http_request_cb: fires when an HTTP request is completed. + * this is when it's time to load the data into the GtkHTML widget. + ************************************************************/ -static gint -ghttp_check_callback(gpointer data) { - gnc_html * html = data; - GList * current; - ghttp_status status; - struct request_info * req; - URLType type; - char * location = NULL; - char * label = NULL; - - /* walk the request list to deal with any complete requests */ - for(current = html->requests; current; current = current->next) { - req = current->data; - - status = ghttp_process(req->request); - switch(status) { - case ghttp_done: - if (ghttp_get_body_len(req->request) > 0) { - { - /* hack alert FIXME: - * This code tries to see if the returned body is - * in fact gnc xml code. If it seems to be, then we - * load it as data, rather than loading it into the - * gtkhtml widget. My gut impression is that we should - * probably be doing this somewhere else, some other - * way, not here .... But I can't think of anything - * better for now. -- linas - */ - const char * bufp = ghttp_get_body(req->request); - bufp += strspn (bufp, " /t/v/f/n/r"); - if (!strncmp (bufp, "uri); - return TRUE; - } - } - - gtk_html_write(GTK_HTML(html->html), - req->handle, - ghttp_get_body(req->request), - ghttp_get_body_len(req->request)); - gtk_html_end(GTK_HTML(html->html), req->handle, GTK_HTML_STREAM_OK); +static void +gnc_html_http_request_cb(const gchar * uri, int completed_ok, + const gchar * body, gint body_len, + gpointer user_data) { + gnc_html * html = user_data; + URLType type; + char * location = NULL; + char * label = NULL; + GList * handles; + GList * current; - type = gnc_html_parse_url(html, req->uri, &location, &label); + handles = g_hash_table_lookup(html->request_info, uri); + + /* handles will be NULL for an HTTP POST transaction, where we are + * displaying the reply data. */ + if(!handles) { + GtkHTMLStream * handle = gtk_html_begin(GTK_HTML(html->html)); + gtk_html_write(GTK_HTML(html->html), handle, body, body_len); + gtk_html_end(GTK_HTML(html->html), handle, GTK_HTML_STREAM_OK); + } + /* otherwise, it's a normal SUBMIT transaction */ + else { + for(current = handles; current; current = current->next) { + /* request completed OK... write the HTML to the handles that + * asked for that URI. */ + if(completed_ok) { + gtk_html_write(GTK_HTML(html->html), (GtkHTMLStream *)(current->data), + body, body_len); + gtk_html_end(GTK_HTML(html->html), (GtkHTMLStream *)(current->data), + GTK_HTML_STREAM_OK); + type = gnc_html_parse_url(html, uri, &location, &label); if(label) { gtk_html_jump_to_anchor(GTK_HTML(html->html), label); } + g_free(location); + g_free(label); + location = label = NULL; } + /* request failed... body is the ghttp error text. */ else { - gtk_html_write(GTK_HTML(html->html), req->handle, error_404, - strlen(error_404)); - gtk_html_end(GTK_HTML(html->html), req->handle, GTK_HTML_STREAM_ERROR); + gtk_html_write(GTK_HTML(html->html), (GtkHTMLStream *)(current->data), + error_start, strlen(error_start)); + gtk_html_write(GTK_HTML(html->html), (GtkHTMLStream *)(current->data), + body, body_len); + gtk_html_write(GTK_HTML(html->html), (GtkHTMLStream *)(current->data), + error_end, strlen(error_end)); + gtk_html_end(GTK_HTML(html->html), (GtkHTMLStream *)(current->data), + GTK_HTML_STREAM_ERROR); } - ghttp_request_destroy(req->request); - req->request = NULL; - req->handle = NULL; - current->data = NULL; - g_free(req); - break; - - case ghttp_error: - gtk_html_write(GTK_HTML(html->html), req->handle, error_start, - strlen(error_start)); - gtk_html_write(GTK_HTML(html->html), req->handle, - ghttp_get_error(req->request), - strlen(ghttp_get_error(req->request))); - gtk_html_write(GTK_HTML(html->html), req->handle, error_end, - strlen(error_end)); - gtk_html_end(GTK_HTML(html->html), req->handle, GTK_HTML_STREAM_ERROR); - ghttp_request_destroy(req->request); - req->request = NULL; - req->handle = NULL; - current->data = NULL; - g_free(req); - break; - - case ghttp_not_done: - break; } - } - /* walk the list again to remove dead requests */ - current = html->requests; - while(current) { - if(current->data == NULL) { - html->requests = g_list_remove_link(html->requests, current); - current = html->requests; + /* now clean up the request info from the hash table */ + handles = NULL; + if(g_hash_table_lookup_extended(html->request_info, uri, + (gpointer *)&location, + (gpointer *)&handles)) { + /* free the URI and the list of handles */ + g_free(location); + g_list_free(handles); + g_hash_table_remove(html->request_info, uri); } - else { - current = current->next; - } - } - - /* if all requests are done, disable the timeout */ - if(html->requests == NULL) { - ghttp_callback_enabled = FALSE; - ghttp_callback_tag = 0; - return FALSE; - } - else { - return TRUE; } } -#ifdef HAVE_OPENSSL -static int -gnc_html_certificate_check_cb(ghttp_request * req, X509 * cert, - void * user_data) { - PINFO("checking SSL certificate..."); - X509_print_fp(stdout, cert); - PINFO(" ... done\n"); - return TRUE; -} -#endif +/************************************************************ + * gnc_html_start_request: starts the gnc-http object working on an + * http/https request. + ************************************************************/ static void gnc_html_start_request(gnc_html * html, gchar * uri, GtkHTMLStream * handle) { - - struct request_info * info = g_new0(struct request_info, 1); + GList * handles = NULL; + gint need_request = FALSE; - info->request = ghttp_request_new(); - info->handle = handle; - info->uri = g_strdup (uri); -#ifdef HAVE_OPENSSL - ghttp_enable_ssl(info->request); - ghttp_set_ssl_certificate_callback(info->request, - gnc_html_certificate_check_cb, - (void *)html); -#endif - ghttp_set_uri(info->request, uri); - ghttp_set_header(info->request, http_hdr_User_Agent, - "gnucash/1.5 (Financial Browser for Linux; http://gnucash.org)"); - ghttp_set_sync(info->request, ghttp_async); - ghttp_prepare(info->request); - ghttp_process(info->request); + /* we want to make a list of handles to fill with this URI. + * multiple handles with the same URI will all get filled when the + * request comes in. */ + handles = g_hash_table_lookup(html->request_info, uri); + if(!handles) { + need_request = TRUE; + } - html->requests = g_list_append(html->requests, info); + handles = g_list_append(handles, handle); + g_hash_table_insert(html->request_info, uri, handles); - /* start the gtk timeout if not started */ - if(!ghttp_callback_enabled) { - ghttp_callback_tag = - gtk_timeout_add(100, ghttp_check_callback, (gpointer)html); - ghttp_callback_enabled = TRUE; + if(need_request) { + gnc_http_start_request(html->http, uri, gnc_html_http_request_cb, + (gpointer)html); } } @@ -633,27 +578,6 @@ gnc_html_guppi_redraw_cb(GtkHTMLEmbedded * eb, } #endif /* USE_GUPPI */ -static char * -unescape_newlines(const gchar * in) { - const char * ip = in; - char * retval = g_strdup(in); - char * op = retval; - - for(ip=in; *ip; ip++) { - if((*ip == '\\') && (*(ip+1)=='n')) { - *op = '\012'; - op++; - ip++; - } - else { - *op = *ip; - op++; - } - } - *op = 0; - return retval; -} - /******************************************************************** * gnc_html_object_requested_cb - called when an applet needs to be @@ -666,100 +590,17 @@ gnc_html_object_requested_cb(GtkHTML * html, GtkHTMLEmbedded * eb, GtkWidget * widg = NULL; gnc_html * gnchtml = data; int retval = FALSE; + GncHTMLObjectCB h; - if(!strcmp(eb->classid, "gnc-guppi-pie")) { -#ifdef USE_GUPPI - widg = gnc_html_embedded_piechart(gnchtml, eb->width, eb->height, - eb->params); -#endif /* USE_GUPPI */ - if(widg) { - gtk_widget_show_all(widg); - gtk_container_add(GTK_CONTAINER(eb), widg); - gtk_widget_set_usize(GTK_WIDGET(eb), eb->width, eb->height); - retval = TRUE; - } - else { - retval = FALSE; - } + if(!eb || !(eb->classid) || !gnc_html_object_handlers) return FALSE; + + h = g_hash_table_lookup(gnc_html_object_handlers, eb->classid); + if(h) { + return h(gnchtml, eb, data); } - else if(!strcmp(eb->classid, "gnc-guppi-bar")) { -#ifdef USE_GUPPI - widg = gnc_html_embedded_barchart(gnchtml, eb->width, eb->height, - eb->params); -#endif /* USE_GUPPI */ - if(widg) { - gtk_widget_show_all(widg); - gtk_container_add(GTK_CONTAINER(eb), widg); - gtk_widget_set_usize(GTK_WIDGET(eb), eb->width, eb->height); - retval = TRUE; - } - else { - retval = FALSE; - } + else { + return FALSE; } - else if(!strcmp(eb->classid, "gnc-guppi-scatter")) { -#ifdef USE_GUPPI - widg = gnc_html_embedded_scatter(gnchtml, eb->width, eb->height, - eb->params); -#endif /* USE_GUPPI */ - if(widg) { - gtk_widget_show_all(widg); - gtk_container_add(GTK_CONTAINER(eb), widg); - gtk_widget_set_usize(GTK_WIDGET(eb), eb->width, eb->height); - retval = TRUE; - } - else { - retval = FALSE; - } - } - else if(!strcmp(eb->classid, "gnc-account-tree")) { - widg = gnc_html_embedded_account_tree(gnchtml, eb->width, eb->height, - eb->params); - if(widg) { - gtk_widget_show_all(widg); - gtk_container_add(GTK_CONTAINER(eb), widg); - gtk_widget_set_usize(GTK_WIDGET(eb), eb->width, eb->height); - retval = TRUE; - } - else { - retval = FALSE; - } - } -#if USE_GPG - else if(!strcmp(eb->classid, "gnc-crypted-html")) { - /* we just want to take the data and stuff it into the widget, - blowing away the active streams. crypted-html contains a - complete HTML page. */ - char * cryptext = unescape_newlines(eb->data); - char * cleartext = gnc_gpg_decrypt(cryptext, strlen(cryptext)); - GtkHTMLStream * handle; - - if(cleartext && cleartext[0]) { - handle = gtk_html_begin(html); - gtk_html_write(html, handle, cleartext, strlen(cleartext)); - gtk_html_end(html, handle, GTK_HTML_STREAM_OK); - retval = TRUE; - } - else { - retval = FALSE; - } - g_free(cleartext); - g_free(cryptext); - } -#endif /* USE_GPG */ - -#if 0 && defined(USE_GUPPI) - if(widg) { - gtk_signal_connect(GTK_OBJECT(eb), "draw_gdk", - GTK_SIGNAL_FUNC(gnc_html_guppi_redraw_cb), - widg); - gtk_signal_connect(GTK_OBJECT(eb), "draw_print", - GTK_SIGNAL_FUNC(gnc_html_guppi_print_cb), - widg); - } -#endif - - return retval; } @@ -896,14 +737,41 @@ static int gnc_html_submit_cb(GtkHTML * html, const gchar * method, const gchar * action, const gchar * encoding, gpointer user_data) { - if(!strcasecmp(method, "get")) { - PINFO("GET submit: m='%s', a='%s', e='%s'", - method, action, encoding); + gnc_html * gnchtml = user_data; + char * location = NULL; + char * new_loc = NULL; + char * label = NULL; + char * submit_encoding = NULL; + char ** action_parts; + URLType type; + GncHTMLActionCB cb; + + type = gnc_html_parse_url(gnchtml, action, &location, &label); + + if(type == URL_TYPE_ACTION) { + if(gnc_html_action_handlers) { + action_parts = g_strsplit(location, "?", 2); + if(action_parts && action_parts[0]) { + cb = g_hash_table_lookup(gnc_html_action_handlers, action_parts[0]); + cb(gnchtml, method, action_parts[0], action_parts[1], encoding); + } + else { + printf("tried to split on ? but failed...\n"); + } + } } - else if(!strcasecmp(method, "post")) { - PINFO("POST submit: m='%s', a='%s', e='%s'", - method, action, encoding); + else { + if(!strcasecmp(method, "get")) { + gnc_html_generic_get_submit(gnchtml, action, encoding); + } + else if(!strcasecmp(method, "post")) { + gnc_html_generic_post_submit(gnchtml, action, encoding); + } } + + g_free(location); + g_free(label); + g_free(new_loc); return TRUE; } @@ -941,7 +809,7 @@ static void gnc_html_open_report(gnc_html * html, const gchar * location, const gchar * label, int newwin) { gnc_report_window * rwin; - GtkHTMLStream * stream; + GtkHTMLStream * handle; /* make a new window if necessary */ if(newwin) { @@ -957,8 +825,8 @@ gnc_html_open_report(gnc_html * html, const gchar * location, html->base_type = URL_TYPE_FILE; html->base_location = NULL; - stream = gtk_html_begin(GTK_HTML(html->html)); - gnc_html_load_to_stream(html, stream, URL_TYPE_REPORT, location, label); + handle = gtk_html_begin(GTK_HTML(html->html)); + gnc_html_load_to_stream(html, handle, URL_TYPE_REPORT, location, label); } @@ -1006,7 +874,7 @@ void gnc_html_show_url(gnc_html * html, URLType type, const gchar * location, const gchar * label, int newwin_hint) { - + GtkHTMLStream * handle; int newwin; @@ -1080,17 +948,17 @@ gnc_html_reload(gnc_html * html) { } -/********************************************************************\ +/******************************************************************** * gnc_html_new * create and set up a new gtkhtml widget. -\********************************************************************/ + ********************************************************************/ gnc_html * gnc_html_new(void) { gnc_html * retval = g_new0(gnc_html, 1); retval->container = gtk_scrolled_window_new(NULL, NULL); - retval->html = gtk_html_new(); + retval->html = gtk_html_new(); gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(retval->container), GTK_POLICY_AUTOMATIC, @@ -1098,8 +966,10 @@ gnc_html_new(void) { gtk_container_add(GTK_CONTAINER(retval->container), GTK_WIDGET(retval->html)); - - retval->history = gnc_html_history_new(); + + retval->request_info = g_hash_table_new(g_str_hash, g_str_equal); + retval->http = gnc_http_new(); + retval->history = gnc_html_history_new(); gtk_widget_ref (retval->container); gtk_object_sink (GTK_OBJECT (retval->container)); @@ -1142,41 +1012,22 @@ gnc_html_new(void) { return retval; } -/********************************************************************\ + +/******************************************************************** * gnc_html_cancel * cancel any outstanding HTML fetch requests. -\********************************************************************/ + ********************************************************************/ + void gnc_html_cancel(gnc_html * html) { - GList * current; - - if(ghttp_callback_enabled == TRUE) { - gtk_timeout_remove(ghttp_callback_tag); - ghttp_callback_enabled = FALSE; - ghttp_callback_tag = 0; - - /* go through and destroy all the requests */ - for(current = html->requests; current; current = current->next) { - if(current->data) { - struct request_info * r = current->data; - ghttp_request_destroy(r->request); - g_free(r->uri); - g_free(r); - current->data = NULL; - } - } - - /* free the list backbone */ - g_list_free(html->requests); - html->requests = NULL; - } + gnc_http_cancel_requests(html->http); } -/********************************************************************\ +/******************************************************************** * gnc_html_destroy * destroy the struct -\********************************************************************/ + ********************************************************************/ void gnc_html_destroy(gnc_html * html) { @@ -1229,44 +1080,41 @@ gnc_html_set_button_cb(gnc_html * html, GncHTMLButtonCB button_cb, html->button_cb_data = data; } -/* ------------------------------------------------------- */ +/************************************************************** + * gnc_html_export : wrapper around the builtin function in gtkhtml + **************************************************************/ static gboolean raw_html_receiver (gpointer engine, - const gchar *data, - guint len, - gpointer user_data) -{ + const gchar *data, + guint len, + gpointer user_data) { FILE *fh = (FILE *) user_data; fwrite (data, len, 1, fh); return TRUE; } void -gnc_html_export(gnc_html * html) -{ +gnc_html_export(gnc_html * html) { const char *filepath; FILE *fh; - + filepath = fileBox (_("Save HTML To File"), NULL, NULL); PINFO (" user selected file=%s\n", filepath); fh = fopen (filepath, "w"); - if (NULL == fh) - { - const char *fmt = _("Could not open the file\n" - " %s\n%s"); - char *buf = g_strdup_printf (fmt, filepath, strerror (errno)); - gnc_error_dialog (buf); - if (buf) g_free (buf); - return; + if (NULL == fh) { + const char *fmt = _("Could not open the file\n" + " %s\n%s"); + char *buf = g_strdup_printf (fmt, filepath, strerror (errno)); + gnc_error_dialog (buf); + if (buf) g_free (buf); + return; } - + gtk_html_save (GTK_HTML(html->html), raw_html_receiver, fh); fclose (fh); } -/* ------------------------------------------------------- */ - void gnc_html_print(gnc_html * html) { PrintSession * ps = gnc_print_session_create(); @@ -1290,29 +1138,197 @@ gnc_html_get_widget(gnc_html * html) { return html->container; } - -#ifdef _TEST_GNC_HTML_ -int -main(int argc, char ** argv) { - - GtkWidget * wind; - gnc_html * html; - - gnome_init("test", "1.0", argc, argv); - gdk_rgb_init(); - gtk_widget_set_default_colormap (gdk_rgb_get_cmap ()); - gtk_widget_set_default_visual (gdk_rgb_get_visual ()); - - wind = gtk_window_new(GTK_WINDOW_TOPLEVEL); - html = gnc_html_new(); - - gtk_container_add(GTK_CONTAINER(wind), GTK_WIDGET(html->container)); - gtk_widget_show_all(wind); - - gnc_html_load_file(html, "test.html"); - - gtk_main(); - +void +gnc_html_register_object_handler(const char * classid, + GncHTMLObjectCB hand) { + if(!gnc_html_object_handlers) { + gnc_html_object_handlers = g_hash_table_new(g_str_hash, g_str_equal); + } + g_hash_table_insert(gnc_html_object_handlers, g_strdup(classid), hand); } -#endif +void +gnc_html_unregister_object_handler(const char * classid) { + gchar * keyptr=NULL; + gchar * valptr=NULL; + + g_hash_table_lookup_extended(gnc_html_object_handlers, + classid, + (gpointer *)&keyptr, + (gpointer *)&valptr); + if(keyptr) { + g_free(keyptr); + g_hash_table_remove(gnc_html_object_handlers, classid); + } +} + +void +gnc_html_register_action_handler(const char * actionid, + GncHTMLActionCB hand) { + if(!gnc_html_action_handlers) { + gnc_html_action_handlers = g_hash_table_new(g_str_hash, g_str_equal); + } + g_hash_table_insert(gnc_html_action_handlers, g_strdup(actionid), hand); +} + +void +gnc_html_unregister_action_handler(const char * actionid) { + gchar * keyptr=NULL; + gchar * valptr=NULL; + + g_hash_table_lookup_extended(gnc_html_action_handlers, + actionid, + (gpointer *)&keyptr, + (gpointer *)&valptr); + if(keyptr) { + g_free(keyptr); + g_hash_table_remove(gnc_html_action_handlers, actionid); + } +} + + +/******************************************************************** + * gnc_html_encode_string + * RFC 1738 encoding of string for submission with an HTML form. + * GPL code lifted from gtkhtml. copyright notice: + * + * Copyright (C) 1997 Martin Jones (mjones@kde.org) + * Copyright (C) 1997 Torben Weis (weis@kde.org) + * Copyright (C) 1999 Helix Code, Inc. + ********************************************************************/ + +char * +gnc_html_encode_string(const char * str) { + static gchar *safe = "$-._!*(),"; /* RFC 1738 */ + unsigned pos = 0; + GString *encoded = g_string_new (""); + gchar buffer[5], *ptr; + guchar c; + + while(pos < strlen(str)) { + c = (unsigned char) str[pos]; + + if ((( c >= 'A') && ( c <= 'Z')) || + (( c >= 'a') && ( c <= 'z')) || + (( c >= '0') && ( c <= '9')) || + (strchr(safe, c))) { + encoded = g_string_append_c (encoded, c); + } + else if ( c == ' ' ) { + encoded = g_string_append_c (encoded, '+'); + } + else if ( c == '\n' ) { + encoded = g_string_append (encoded, "%0D%0A"); + } + else if ( c != '\r' ) { + sprintf( buffer, "%%%02X", (int)c ); + encoded = g_string_append (encoded, buffer); + } + pos++; + } + + ptr = encoded->str; + + g_string_free (encoded, FALSE); + + return (char *)ptr; +} + + +/******************************************************************** + * gnc_html_generic_get_submit() : normal 'get' submit method. + ********************************************************************/ + +void +gnc_html_generic_get_submit(gnc_html * html, const char * action, + const char * encoding) { + URLType type; + char * location = NULL; + char * label = NULL; + char * fullurl = NULL; + + type = gnc_html_parse_url(html, action, &location, &label); + fullurl = g_strconcat(location, "?", encoding, NULL); + gnc_html_show_url(html, type, fullurl, label, 0); + + g_free(location); + g_free(label); + g_free(fullurl); +} + + +/******************************************************************** + * gnc_html_generic_post_submit() : normal 'post' submit method. + ********************************************************************/ + +void +gnc_html_generic_post_submit(gnc_html * html, const char * action, + const char * encoding) { + char * htmlstr = g_strconcat(encoding, + "&submit=submit", + NULL); + gnc_http_start_post(html->http, action, + "application/x-www-form-urlencoded", + htmlstr, strlen(htmlstr), + gnc_html_http_request_cb, html); +} + + +/******************************************************************** + * gnc_html_multipart_post_submit() : this is really sort of useless + * but I'll make it better later. It's useless because FTMP CGI/php + * don't properly decode the urlencoded values. + ********************************************************************/ + +void +gnc_html_multipart_post_submit(gnc_html * html, const char * action, + const char * encoding) { + char * htmlstr = g_strdup(""); + char * next_htmlstr; + char * next_pair = g_strconcat(encoding, "&submit=submit", NULL); + char * name = NULL; + char * value = NULL; + char * extr_name = NULL; + char * extr_value = NULL; + char * length_line = NULL; + + while(next_pair) { + name = next_pair; + if((value = strchr(name, '='))) { + extr_name = g_strndup(name, value-name); + next_pair = strchr(value, '&'); + if(next_pair) { + extr_value = g_strndup(value+1, next_pair-value-1); + next_pair++; + } + else { + extr_value = g_strdup(value+1); + } + next_htmlstr = + g_strconcat(htmlstr, + "--XXXgncXXX\r\n", + "Content-Disposition: form-data; name=\"", + extr_name, "\"\r\n", + "Content-Type: application/x-www-form-urlencoded\r\n\r\n", + extr_value, "\r\n", + NULL); + + g_free(htmlstr); + htmlstr = next_htmlstr; + next_htmlstr = NULL; + } + else { + next_pair = NULL; + } + } + + next_htmlstr = g_strconcat(htmlstr, "--XXXgncXXX--\r\n", NULL); + g_free(htmlstr); + htmlstr = next_htmlstr; + next_htmlstr = NULL; + gnc_http_start_post(html->http, action, + "multipart/form-data; boundary=XXXgncXXX", + htmlstr, strlen(htmlstr), + gnc_html_http_request_cb, html); + g_free(htmlstr); +} diff --git a/src/gnome/gnc-html.h b/src/gnome/gnc-html.h index ed2d969093..9b91adeae7 100644 --- a/src/gnome/gnc-html.h +++ b/src/gnome/gnc-html.h @@ -1,5 +1,5 @@ -/********************************************************************\ - * gnc-html.h -- display html with gnc special tags +/******************************************************************** + * gnc-html.h -- display html with gnc special tags * * Copyright (C) 2000 Bill Gribble * * * * This program is free software; you can redistribute it and/or * @@ -23,15 +23,10 @@ #ifndef __GNC_HTML_H__ #define __GNC_HTML_H__ -#include -#include +#include #include - -/* - * The gnc_html_export() routine will export the html displayed to a - * file. It will pop up a file dialog box to ask user for a - * filename. - */ +#include +#include typedef enum { URL_TYPE_FILE, URL_TYPE_JUMP, URL_TYPE_HTTP, URL_TYPE_FTP, @@ -40,13 +35,15 @@ typedef enum { URL_TYPE_FILE, URL_TYPE_JUMP, URL_TYPE_REPORT, /* for gnucash report popups */ URL_TYPE_SCHEME, /* for scheme code evaluation */ URL_TYPE_HELP, /* for a gnucash help window */ + URL_TYPE_XMLDATA, /* links to gnucash XML data files */ + URL_TYPE_ACTION, /* for special SUBMIT actions */ URL_TYPE_OTHER } URLType; #include "gnc-html-history.h" typedef struct _gnc_html gnc_html; -typedef int (*GncHTMLUrltypeCB)(URLType ut); +typedef int (* GncHTMLUrltypeCB)(URLType ut); typedef void (* GncHTMLFlyoverCB)(gnc_html * html, const char * url, gpointer data); typedef void (* GncHTMLLoadCB)(gnc_html * html, URLType type, @@ -54,7 +51,11 @@ typedef void (* GncHTMLLoadCB)(gnc_html * html, URLType type, gpointer data); typedef int (* GncHTMLButtonCB)(gnc_html * html, GdkEventButton * event, gpointer data); - +typedef int (* GncHTMLObjectCB)(gnc_html * html, GtkHTMLEmbedded * eb, + gpointer data); +typedef int (* GncHTMLActionCB)(gnc_html * html, const char * method, + const char * action, const char * act_args, + const char * encoding); gnc_html * gnc_html_new(void); void gnc_html_destroy(gnc_html * html); void gnc_html_show_url(gnc_html * html, @@ -65,9 +66,32 @@ void gnc_html_export(gnc_html * html); void gnc_html_print(gnc_html * html); void gnc_html_cancel(gnc_html * html); -URLType -gnc_html_parse_url(gnc_html * html, const gchar * url, - char ** url_location, char ** url_label); +/* object handlers deal with objects in HTML. + * the handlers are looked up at object load time. */ +void gnc_html_register_object_handler(const char * classid, + GncHTMLObjectCB hand); +void gnc_html_unregister_object_handler(const char * classid); + +/* action handlers deal with submitting forms of the type + * . Normal get/post http: + * forms are handled as would be expected, with no callback. */ +void gnc_html_register_action_handler(const char * action, + GncHTMLActionCB hand); +void gnc_html_unregister_action_handler(const char * action); + +/* default action handlers for GET and POST methods. 'generic_post' + * is the trivial application/x-www-form-urlencoded submit, + * multipart-post is a multipart/form-data submit. */ +void gnc_html_generic_get_submit(gnc_html * html, const char * act, + const char * encoding); +void gnc_html_generic_post_submit(gnc_html * html, const char * act, + const char * encoding); +void gnc_html_multipart_post_submit(gnc_html * html, const char * a, + const char * encoding); + +URLType gnc_html_parse_url(gnc_html * html, const gchar * url, + char ** url_location, char ** url_label); +char * gnc_html_encode_string(const char * in); gnc_html_history * gnc_html_get_history(gnc_html * html); GtkWidget * gnc_html_get_widget(gnc_html * html); diff --git a/src/gnome/gnc-http.c b/src/gnome/gnc-http.c new file mode 100644 index 0000000000..9b3623722b --- /dev/null +++ b/src/gnome/gnc-http.c @@ -0,0 +1,249 @@ +/******************************************************************** + * gnc-http.c -- handle processing of HTTP requests via gnome-http * + * Copyright (C) 2001 Bill Gribble * + * * + * This program 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 2 of * + * the License, or (at your option) any later version. * + * * + * This program 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 this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + ********************************************************************/ + +#include "config.h" + +#ifdef HAVE_OPENSSL +#include +#include +#include +#include +#include +#include +#endif + +#include +#include +#include +#include + +#include "gnc-http.h" + +struct _gnc_http { + GList * requests; + guint callback_tag; + gint callback_enabled; +}; + +struct request_info { + gchar * uri; + ghttp_request * request; + GncHTTPRequestCB callback; + gpointer callback_data; +}; + +gnc_http * +gnc_http_new(void) { + gnc_http * ret = g_new0(struct _gnc_http, 1); + ret->requests = NULL; + ret->callback_tag = 0; + ret->callback_enabled = FALSE; + return ret; +} + +void +gnc_http_destroy(gnc_http * http) { + gnc_http_cancel_requests(http); + g_free(http); +} + +void +gnc_http_cancel_requests(gnc_http * http) { + GList * current; + + if(http->callback_enabled == TRUE) { + /* FIXME : should replace this with glib idle function -- bg */ + gtk_timeout_remove(http->callback_tag); + http->callback_enabled = FALSE; + http->callback_tag = 0; + + /* go through and destroy all the requests */ + for(current = http->requests; current; current = current->next) { + if(current->data) { + struct request_info * r = current->data; + ghttp_request_destroy(r->request); + g_free(r->uri); + g_free(r); + current->data = NULL; + } + } + + /* free the list backbone */ + g_list_free(http->requests); + http->requests = NULL; + } +} + +static gint +ghttp_check_callback(gpointer data) { + gnc_http * http = data; + GList * current; + ghttp_status status; + struct request_info * req; + + /* walk the request list to deal with any complete requests */ + for(current = http->requests; current; current = current->next) { + req = current->data; + + status = ghttp_process(req->request); + switch(status) { + case ghttp_done: + if(req->callback) { + (req->callback)(req->uri, TRUE, + ghttp_get_body(req->request), + ghttp_get_body_len(req->request), + req->callback_data); + } + ghttp_request_destroy(req->request); + req->request = NULL; + current->data = NULL; + g_free(req); + break; + + case ghttp_error: + if(req->callback) { + (req->callback)(req->uri, FALSE, + ghttp_get_error(req->request), + strlen(ghttp_get_error(req->request)), + req->callback_data); + } + ghttp_request_destroy(req->request); + req->request = NULL; + current->data = NULL; + g_free(req); + break; + + case ghttp_not_done: + break; + } + } + + /* walk the list again to remove dead requests */ + current = http->requests; + while(current) { + if(current->data == NULL) { + http->requests = g_list_remove_link(http->requests, current); + current = http->requests; + } + else { + current = current->next; + } + } + + /* if all requests are done, disable the timeout */ + if(http->requests == NULL) { + http->callback_enabled = FALSE; + http->callback_tag = 0; + return FALSE; + } + else { + return TRUE; + } +} + + +#ifdef HAVE_OPENSSL +static int +gnc_http_certificate_check_cb(ghttp_request * req, X509 * cert, + void * user_data) { + PINFO("checking SSL certificate..."); + X509_print_fp(stdout, cert); + PINFO(" ... done\n"); + return TRUE; +} +#endif + +void +gnc_http_start_request(gnc_http * http, const gchar * uri, + GncHTTPRequestCB cb, gpointer user_data) { + + struct request_info * info = g_new0(struct request_info, 1); + + info->request = ghttp_request_new(); + info->uri = g_strdup (uri); + info->callback = cb; + info->callback_data = user_data; + +#ifdef HAVE_OPENSSL + ghttp_enable_ssl(info->request); + ghttp_set_ssl_certificate_callback(info->request, + gnc_http_certificate_check_cb, + (void *)http); +#endif + ghttp_set_uri(info->request, (char *)uri); + ghttp_set_header(info->request, http_hdr_User_Agent, + "gnucash/1.5 (Financial Browser for Linux; http://gnucash.org)"); + ghttp_set_sync(info->request, ghttp_async); + ghttp_set_type(info->request, ghttp_type_get); + ghttp_prepare(info->request); + ghttp_process(info->request); + + http->requests = g_list_append(http->requests, info); + + /* start the gtk timeout if not started */ + /* FIXME : should replace this with glib idle function -- bg */ + if(!http->callback_enabled) { + + http->callback_tag = + gtk_timeout_add(100, ghttp_check_callback, (gpointer)http); + http->callback_enabled = TRUE; + } +} + +void +gnc_http_start_post(gnc_http * http, const char * uri, + const char * content_type, + const char * data, int datalen, + GncHTTPRequestCB cb, gpointer user_data) { + struct request_info * info = g_new0(struct request_info, 1); + + info->request = ghttp_request_new(); + info->uri = g_strdup (uri); + info->callback = cb; + info->callback_data = user_data; +#ifdef HAVE_OPENSSL + ghttp_enable_ssl(info->request); + ghttp_set_ssl_certificate_callback(info->request, + gnc_http_certificate_check_cb, + (void *)http); +#endif + ghttp_set_uri(info->request, uri); + ghttp_set_header(info->request, http_hdr_User_Agent, + "gnucash/1.5 (Financial Browser for Linux; http://gnucash.org)"); + ghttp_set_header(info->request, http_hdr_Content_Type, content_type); + ghttp_set_sync(info->request, ghttp_async); + ghttp_set_type(info->request, ghttp_type_post); + ghttp_set_body(info->request, data, datalen); + + ghttp_prepare(info->request); + ghttp_process(info->request); + + http->requests = g_list_append(http->requests, info); + + /* start the gtk timeout if not started */ + if(!http->callback_enabled) { + http->callback_tag = + gtk_timeout_add(100, ghttp_check_callback, (gpointer)http); + http->callback_enabled = TRUE; + } +} + diff --git a/src/gnome/gnc-http.h b/src/gnome/gnc-http.h new file mode 100644 index 0000000000..f6e867709f --- /dev/null +++ b/src/gnome/gnc-http.h @@ -0,0 +1,43 @@ +/******************************************************************** + * gnc-http.h -- handle HTTP requests. thin wrapper on gnome-http. * + * Copyright (C) 2001 Bill Gribble * + * * + * This program 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 2 of * + * the License, or (at your option) any later version. * + * * + * This program 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 this program; if not, contact: * + * * + * Free Software Foundation Voice: +1-617-542-5942 * + * 59 Temple Place - Suite 330 Fax: +1-617-542-2652 * + * Boston, MA 02111-1307, USA gnu@gnu.org * + ********************************************************************/ + +#ifndef __GNC_HTTP_H__ +#define __GNC_HTTP_H__ + +#include + +typedef struct _gnc_http gnc_http; + +typedef void (* GncHTTPRequestCB)(const char * uri, int completed_ok, + const char * body, int body_len, + gpointer user_data); +gnc_http * gnc_http_new(void); +void gnc_http_destroy(gnc_http * html); +void gnc_http_start_request(gnc_http * http, const char * uri, + GncHTTPRequestCB cb, gpointer user_data); +void gnc_http_start_post(gnc_http * http, const char * uri, + const char * content_type, + const char * body, int body_len, + GncHTTPRequestCB cb, gpointer user_data); +void gnc_http_cancel_requests(gnc_http * http); + +#endif diff --git a/src/gnome/top-level.c b/src/gnome/top-level.c index 0f4051c357..9428058c14 100644 --- a/src/gnome/top-level.c +++ b/src/gnome/top-level.c @@ -25,11 +25,7 @@ #include "config.h" #include -#include #include -#ifdef USE_GUPPI -#include -#endif #include #include @@ -50,6 +46,13 @@ #include "gnc-component-manager.h" #include "gnc-engine-util.h" #include "gnc-splash.h" +#include "gnc-html-actions.h" +#ifdef USE_GUPPI +#include "gnc-html-guppi.h" +#endif +#ifdef USE_GPG +#include "gnc-gpg.h" +#endif #include "gnc-ui.h" #include "gnc.h" #include "gnucash-color.h" @@ -212,9 +215,15 @@ gnucash_ui_init(void) gtk_widget_set_default_colormap (gdk_rgb_get_cmap ()); gtk_widget_set_default_visual (gdk_rgb_get_visual ()); + /* load default HTML action handlers */ + gnc_html_actions_init(); #ifdef USE_GUPPI - /* initialize guppi */ - guppi_tank_init(); + /* initialize guppi handling in gnc-html */ + gnc_html_guppi_init(); +#endif +#ifdef USE_GPG + /* initialize gpg handling in gnc-html */ + gnc_gpg_init(); #endif /* put up splash screen */ diff --git a/src/gnome/window-help.c b/src/gnome/window-help.c index aa0f697427..897bc0164a 100644 --- a/src/gnome/window-help.c +++ b/src/gnome/window-help.c @@ -655,9 +655,22 @@ gnc_help_window_new (void) { void gnc_help_window_destroy(gnc_help_window * help) { - if (!help) return; + gnc_unregister_gui_component_by_data (WINDOW_HELP_CM_CLASS, help); + + gtk_signal_disconnect_by_func(GTK_OBJECT(help->toplevel), + GTK_SIGNAL_FUNC(gnc_help_window_destroy_cb), + (gpointer)help); + /* close the help index db */ + if(help->index_db) { + help->index_db->close(help->index_db); + } + + /* take care of the gnc-html object specially */ + gtk_widget_ref(gnc_html_get_widget(help->html)); + gnc_html_destroy(help->html); + gtk_widget_destroy(GTK_WIDGET(help->toplevel)); } diff --git a/src/scm/html-text.scm b/src/scm/html-text.scm index bc45266711..c266b846b1 100644 --- a/src/scm/html-text.scm +++ b/src/scm/html-text.scm @@ -118,6 +118,34 @@ #f entities))) +;; I'm not entirely pleased about the way this works, but I can't +;; really see a way around it. It still works within the style +;; system, but it flattens out its children's lists prematurely. Has +;; to, to pass them as args to sprintf. + +(define (gnc:html-markup/format format . entities) + (lambda (doc) + (apply + sprintf #f format + (map + (lambda (elt) + (let ((rendered-elt + (cond ((procedure? elt) + (elt doc)) + ((gnc:html-object? elt) + (gnc:html-object-render elt doc)) + (#t + (gnc:html-document-render-data doc elt))))) + (cond + ((string? rendered-elt) + rendered-elt) + ((list? rendered-elt) + (apply string-append (gnc:report-tree-collapse rendered-elt))) + (#t + (simple-format "hold on there podner. form='~s'\n" rendered-elt) + "")))) + entities)))) + (define (gnc:html-markup-p . rest) (apply gnc:html-markup "p" rest)) @@ -182,9 +210,10 @@ (gnc:html-document-push-style doc (gnc:html-text-style p)) (for-each-in-order (lambda (elt) - (if (procedure? elt) - (push (elt doc)) - (push (gnc:html-document-render-data doc elt)))) + (cond ((procedure? elt) + (push (elt doc))) + (#t + (push (gnc:html-document-render-data doc elt))))) (gnc:html-text-body p)) (gnc:html-document-pop-style doc) (gnc:html-style-table-uncompile (gnc:html-text-style p)) @@ -195,10 +224,11 @@ (push (lambda (l) (set! retval (cons l retval))))) (push (gnc:html-document-markup-start doc markup attrib)) (for-each-in-order - (lambda (elt) - (if (procedure? elt) - (push (elt doc)) - (push (gnc:html-document-render-data doc elt)))) + (lambda (elt) + (cond ((procedure? elt) + (push (elt doc))) + (#t + (push (gnc:html-document-render-data doc elt))))) entities) (if end-tag? (push (gnc:html-document-markup-end doc markup))) diff --git a/src/scm/report/hello-world.scm b/src/scm/report/hello-world.scm index 721500b690..345079b9be 100644 --- a/src/scm/report/hello-world.scm +++ b/src/scm/report/hello-world.scm @@ -357,16 +357,19 @@ new, totally cool report, consult the mailing list ") (gnc:html-markup-b date-string2) ".") (gnc:html-markup-p - (_ "The relative date option is ") - (gnc:html-markup-b rel-date-string) ".") + (gnc:html-markup/format + (_ "The relative date option is %a.") + (gnc:html-markup-b rel-date-string))) (gnc:html-markup-p - (_ "The combination date option is ") - (gnc:html-markup-b combo-date-string) ".") + (gnc:html-markup/format + (_ "The combination date option is %a.") + (gnc:html-markup-b combo-date-string))) (gnc:html-markup-p - (_ "The number option is ") - (gnc:html-markup-b (number->string num-val)) ".") + (gnc:html-markup/format + (_ "The number option is %a.") + (gnc:html-markup-b (number->string num-val)))) ;; Here we print the value of the number option formatted as ;; currency. When printing currency values, you should use @@ -375,10 +378,11 @@ new, totally cool report, consult the mailing list ") ;; appropriately in the current locale. Don't try to format ;; it yourself -- it will be wrong in other locales. (gnc:html-markup-p - (_ "The number option formatted as currency is ") - (gnc:html-markup-b - (gnc:amount->string num-val (gnc:default-print-info #f)))))) - + (gnc:html-markup/format + (_ "The number option formatted as currency is %a.") + (gnc:html-markup-b + (gnc:amount->string num-val (gnc:default-print-info #f))))))) + ;; you can add as many objects as you want. Here's another ;; one. We'll make a single-column table of the selected list ;; options just for grins.