1 /********************************************************************
2  * gnc-html.c -- display HTML with some special gnucash tags.       *
3  *                                                                  *
4  * Copyright (C) 2000 Bill Gribble <grib@billgribble.com>           *
5  * Copyright (C) 2001 Linas Vepstas <linas@linas.org>               *
6  *                                                                  *
7  * This program is free software; you can redistribute it and/or    *
8  * modify it under the terms of the GNU General Public License as   *
9  * published by the Free Software Foundation; either version 2 of   *
10  * the License, or (at your option) any later version.              *
11  *                                                                  *
12  * This program is distributed in the hope that it will be useful,  *
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
15  * GNU General Public License for more details.                     *
16  *                                                                  *
17  * You should have received a copy of the GNU General Public License*
18  * along with this program; if not, contact:                        *
19  *                                                                  *
20  * Free Software Foundation           Voice:  +1-617-542-5942       *
21  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
22  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
23  ********************************************************************/
24 
25 #include <config.h>
26 
27 #include <platform.h>
28 #if PLATFORM(WINDOWS)
29 #include <windows.h>
30 #endif
31 
32 #include <gtk/gtk.h>
33 #include <glib/gi18n.h>
34 #include <glib/gstdio.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <stdlib.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <fcntl.h>
41 #include <unistd.h>
42 #include <regex.h>
43 
44 #include "Account.h"
45 #include "print-session.h"
46 #include "gnc-engine.h"
47 #include "gnc-html.h"
48 #include "gnc-html-history.h"
49 
50 /* indicates the debugging module that this .o belongs to.  */
51 static QofLogModule log_module = GNC_MOD_HTML;
52 
53 /* hashes for URLType -> protocol and protocol -> URLType */
54 static GHashTable * gnc_html_type_to_proto_hash = NULL;
55 GHashTable * gnc_html_proto_to_type_hash = NULL;
56 
57 /* hashes an HTML <object classid="ID"> classid to a handler function */
58 GHashTable* gnc_html_object_handlers = NULL;
59 
60 /* hashes handlers for loading different URLType data */
61 GHashTable* gnc_html_stream_handlers = NULL;
62 
63 /* hashes handlers for handling different URLType data */
64 GHashTable* gnc_html_url_handlers = NULL;
65 
66 /* hashes an HTML <object classid="ID"> classid to a handler function */
67 extern GHashTable* gnc_html_object_handlers;
68 
69 G_DEFINE_ABSTRACT_TYPE(GncHtml, gnc_html, GTK_TYPE_BIN)
70 
71 static void gnc_html_class_init( GncHtmlClass* klass );
72 static void gnc_html_dispose( GObject* obj );
73 static void gnc_html_finalize( GObject* obj );
74 /*
75 #define GNC_HTML_GET_PRIVATE(o) \
76      ((GncHtmlPrivate*)g_type_instance_get_private((GTypeInstance*)o, GNC_TYPE_HTML))
77 */
78 #define GNC_HTML_GET_PRIVATE(o) (GNC_HTML(o)->priv)
79 
80 #include "gnc-html-p.h"
81 
82 static void
gnc_html_class_init(GncHtmlClass * klass)83 gnc_html_class_init( GncHtmlClass* klass )
84 {
85     GObjectClass *gobject_class = G_OBJECT_CLASS(klass);
86 
87     gobject_class->dispose = gnc_html_dispose;
88     gobject_class->finalize = gnc_html_finalize;
89 
90     klass->show_url = NULL;
91     klass->show_data = NULL;
92     klass->reload = NULL;
93     klass->copy_to_clipboard = NULL;
94     klass->export_to_file = NULL;
95     klass->print = NULL;
96     klass->cancel = NULL;
97     klass->parse_url = NULL;
98     klass->set_parent = NULL;
99 }
100 
101 static void
gnc_html_init(GncHtml * self)102 gnc_html_init( GncHtml* self )
103 {
104     GncHtmlPrivate* priv;
105     priv = self->priv = g_new0( GncHtmlPrivate, 1 );
106 
107     priv->container = gtk_scrolled_window_new( NULL, NULL );
108     gtk_scrolled_window_set_policy( GTK_SCROLLED_WINDOW(priv->container),
109                                     GTK_POLICY_AUTOMATIC,
110                                     GTK_POLICY_AUTOMATIC );
111 
112     priv->request_info = g_hash_table_new( g_str_hash, g_str_equal );
113     priv->history = gnc_html_history_new();
114 }
115 
116 static void
gnc_html_dispose(GObject * obj)117 gnc_html_dispose( GObject* obj )
118 {
119     GncHtml* self = GNC_HTML(obj);
120     GncHtmlPrivate* priv = GNC_HTML_GET_PRIVATE(self);
121 
122     if ( priv->container != NULL )
123     {
124         gtk_widget_destroy( GTK_WIDGET(priv->container) );
125         g_object_unref( G_OBJECT(priv->container) );
126         priv->container = NULL;
127     }
128     if ( priv->request_info != NULL )
129     {
130         g_hash_table_destroy( priv->request_info );
131         priv->request_info = NULL;
132     }
133     if ( priv->history != NULL )
134     {
135         gnc_html_history_destroy( priv->history );
136         priv->history = NULL;
137     }
138 
139     G_OBJECT_CLASS(gnc_html_parent_class)->dispose( obj );
140 }
141 
142 static void
gnc_html_finalize(GObject * obj)143 gnc_html_finalize( GObject* obj )
144 {
145     GncHtml* self = GNC_HTML(obj);
146 
147     if ( self->priv != NULL )
148     {
149         g_free( self->priv );
150         self->priv = NULL;
151     }
152 
153     G_OBJECT_CLASS(gnc_html_parent_class)->finalize( obj );
154 }
155 
156 /***********************************************************************************/
157 
158 static char*
extract_machine_name(const gchar * path)159 extract_machine_name( const gchar* path )
160 {
161     gchar machine_rexp[] = "^(//[^/]*)/*(.*)?$";
162     regex_t compiled_m;
163     regmatch_t match[4];
164     gchar* machine = NULL;
165 
166     if ( path == NULL ) return NULL;
167 
168     regcomp( &compiled_m, machine_rexp, REG_EXTENDED );
169 
170     /* step 1: split the machine name away from the path
171      * components */
172     if ( !regexec( &compiled_m, path, 4, match, 0 ) )
173     {
174         /* $1 is the machine name */
175         if ( match[1].rm_so != -1 )
176         {
177             machine = g_strndup( path + match[1].rm_so, match[1].rm_eo - match[1].rm_so );
178         }
179     }
180     regfree(&compiled_m);
181     return machine;
182 }
183 
184 /********************************************************************
185  * gnc_html_parse_url
186  * this takes a URL and determines the protocol type, location, and
187  * possible anchor name from the URL.
188  ********************************************************************/
189 
190 URLType
gnc_html_parse_url(GncHtml * self,const gchar * url,gchar ** url_location,gchar ** url_label)191 gnc_html_parse_url( GncHtml* self, const gchar* url,
192                     gchar** url_location, gchar** url_label )
193 {
194     gchar uri_rexp[] = "^(([^:][^:]+):)?([^#]+)?(#(.*))?$";
195     regex_t compiled;
196     regmatch_t match[6];
197     gchar* protocol = NULL;
198     gchar* path = NULL;
199     gchar* label = NULL;
200     gboolean found_protocol = FALSE;
201     gboolean found_path = FALSE;
202     gboolean found_label = FALSE;
203     URLType retval;
204     GncHtmlPrivate* priv = GNC_HTML_GET_PRIVATE(self);
205 
206     g_return_val_if_fail( self != NULL, NULL );
207     g_return_val_if_fail( GNC_IS_HTML(self), NULL );
208 
209     DEBUG( "parsing %s, base_location %s",
210            url ? url : "(null)",
211            self ? (priv->base_location ? priv->base_location
212                    : "(null base_location)")
213                : "(null html)");
214 
215     regcomp( &compiled, uri_rexp, REG_EXTENDED );
216 
217     if ( !regexec( &compiled, url, 6, match, 0 ) )
218     {
219         if ( match[2].rm_so != -1 )
220         {
221             protocol = g_new0( gchar, match[2].rm_eo - match[2].rm_so + 1 );
222             strncpy( protocol, url + match[2].rm_so, match[2].rm_eo - match[2].rm_so );
223             protocol[match[2].rm_eo - match[2].rm_so] = 0;
224             found_protocol = TRUE;
225         }
226         if ( match[3].rm_so != -1 )
227         {
228             path = g_new0( gchar, match[3].rm_eo - match[3].rm_so + 1 );
229             strncpy( path, url + match[3].rm_so, match[3].rm_eo - match[3].rm_so );
230             path[match[3].rm_eo - match[3].rm_so] = 0;
231             found_path = TRUE;
232         }
233         if ( match[5].rm_so != -1 )
234         {
235             label = g_new0( gchar, match[5].rm_eo - match[5].rm_so + 1 );
236             strncpy( label, url + match[5].rm_so, match[5].rm_eo - match[5].rm_so );
237             label[match[5].rm_eo - match[5].rm_so] = 0;
238             found_label = TRUE;
239         }
240     }
241 
242     regfree( &compiled );
243 
244     if ( found_protocol )
245     {
246         retval = g_hash_table_lookup( gnc_html_proto_to_type_hash, protocol );
247         if ( retval == NULL )
248         {
249             PWARN( "unhandled URL type for '%s'", url ? url : "(null)" );
250             retval = URL_TYPE_OTHER;
251         }
252     }
253     else if ( found_label && !found_path )
254     {
255         retval = URL_TYPE_JUMP;
256     }
257     else
258     {
259         if ( self )
260         {
261             retval = priv->base_type;
262         }
263         else
264         {
265             retval = URL_TYPE_FILE;
266         }
267     }
268 
269     g_free( protocol );
270 
271     if ( !g_strcmp0( retval, URL_TYPE_FILE ) )
272     {
273         if ( !found_protocol && path && self && priv->base_location )
274         {
275             if ( g_path_is_absolute( path ) )
276             {
277                 *url_location = g_strdup( path );
278             }
279             else
280             {
281                 *url_location = g_build_filename( priv->base_location, path, (gchar*)NULL );
282             }
283             g_free( path );
284         }
285         else
286         {
287             *url_location = g_strdup( path );
288             g_free( path );
289         }
290 
291     }
292     else if ( !g_strcmp0( retval, URL_TYPE_JUMP ) )
293     {
294         *url_location = NULL;
295         g_free( path );
296 
297     }
298     else
299     {
300         /* case URL_TYPE_OTHER: */
301 
302         if ( !found_protocol && path && self && priv->base_location )
303         {
304             if ( g_path_is_absolute( path ) )
305             {
306                 *url_location = g_build_filename( extract_machine_name( priv->base_location ),
307                                                   path, (gchar*)NULL );
308             }
309             else
310             {
311                 *url_location = g_build_filename( priv->base_location, path, (gchar*)NULL );
312             }
313             g_free( path );
314         }
315         else
316         {
317             *url_location = g_strdup( path );
318             g_free( path );
319         }
320     }
321 
322     *url_label = label;
323     return retval;
324 }
325 
326 /********************************************************************
327  * gnc_html_show_data
328  * display some HTML that the creator of the gnc-html got from
329  * somewhere.
330  ********************************************************************/
331 
332 void
gnc_html_show_data(GncHtml * self,const gchar * data,int datalen)333 gnc_html_show_data( GncHtml* self, const gchar* data, int datalen )
334 {
335     g_return_if_fail( self != NULL );
336     g_return_if_fail( GNC_IS_HTML(self) );
337 
338     if ( GNC_HTML_GET_CLASS(self)->show_data != NULL )
339     {
340         GNC_HTML_GET_CLASS(self)->show_data( self, data, datalen );
341     }
342     else
343     {
344         DEBUG( "'show_data' not implemented" );
345     }
346 }
347 
348 
349 /********************************************************************
350  * gnc_html_show_url
351  *
352  * open a URL.  This is called when the user clicks a link or
353  * for the creator of the gnc_html window to explicitly request
354  * a URL.
355  ********************************************************************/
356 
357 void
gnc_html_show_url(GncHtml * self,URLType type,const gchar * location,const gchar * label,gboolean new_window_hint)358 gnc_html_show_url( GncHtml* self, URLType type,
359                    const gchar* location, const gchar* label,
360                    gboolean new_window_hint )
361 {
362     URLType lc_type = NULL;
363 
364     g_return_if_fail( self != NULL );
365     g_return_if_fail( GNC_IS_HTML(self) );
366 
367     lc_type = g_ascii_strdown (type, -1);
368     if ( GNC_HTML_GET_CLASS(self)->show_url != NULL )
369     {
370         GNC_HTML_GET_CLASS(self)->show_url( self, lc_type, location, label, new_window_hint );
371     }
372     else
373     {
374         DEBUG( "'show_url' not implemented" );
375     }
376 
377     g_free (lc_type);
378 }
379 
380 
381 /********************************************************************
382  * gnc_html_reload
383  * reload the current page
384  * if force_rebuild is TRUE, the report is recreated, if FALSE, report
385  * is reloaded ib the view
386  ********************************************************************/
387 
388 void
gnc_html_reload(GncHtml * self,gboolean force_rebuild)389 gnc_html_reload( GncHtml* self, gboolean force_rebuild )
390 {
391     g_return_if_fail( self != NULL );
392     g_return_if_fail( GNC_IS_HTML(self) );
393 
394     if ( GNC_HTML_GET_CLASS(self)->reload != NULL )
395     {
396         GNC_HTML_GET_CLASS(self)->reload( self, force_rebuild );
397     }
398     else
399     {
400         DEBUG( "'reload' not implemented" );
401     }
402 }
403 
404 /********************************************************************
405  * gnc_html_cancel
406  * cancel any outstanding HTML fetch requests.
407  ********************************************************************/
408 
409 void
gnc_html_cancel(GncHtml * self)410 gnc_html_cancel( GncHtml* self )
411 {
412     g_return_if_fail( self != NULL );
413     g_return_if_fail( GNC_IS_HTML(self) );
414 
415     if ( GNC_HTML_GET_CLASS(self)->cancel != NULL )
416     {
417         GNC_HTML_GET_CLASS(self)->cancel( self );
418     }
419     else
420     {
421         DEBUG( "'cancel' not implemented" );
422     }
423 }
424 
425 
426 /********************************************************************
427  * gnc_html_destroy
428  * destroy the struct
429  ********************************************************************/
430 
431 void
gnc_html_destroy(GncHtml * self)432 gnc_html_destroy( GncHtml* self )
433 {
434     g_return_if_fail( self != NULL );
435     g_return_if_fail( GNC_IS_HTML(self) );
436 
437     if ( g_object_is_floating( G_OBJECT(self) ) )
438     {
439         (void)g_object_ref_sink( G_OBJECT(self) );
440     }
441 
442     g_object_unref( G_OBJECT(self) );
443 }
444 
445 void
gnc_html_set_urltype_cb(GncHtml * self,GncHTMLUrltypeCB urltype_cb)446 gnc_html_set_urltype_cb( GncHtml* self, GncHTMLUrltypeCB urltype_cb )
447 {
448     GncHtmlPrivate* priv;
449 
450     g_return_if_fail( self != NULL );
451     g_return_if_fail( GNC_IS_HTML(self) );
452 
453     priv = GNC_HTML_GET_PRIVATE(self);
454     priv->urltype_cb = urltype_cb;
455 }
456 
457 void
gnc_html_set_load_cb(GncHtml * self,GncHTMLLoadCB load_cb,gpointer data)458 gnc_html_set_load_cb( GncHtml* self, GncHTMLLoadCB load_cb, gpointer data )
459 {
460     GncHtmlPrivate* priv;
461 
462     g_return_if_fail( self != NULL );
463     g_return_if_fail( GNC_IS_HTML(self) );
464 
465     priv = GNC_HTML_GET_PRIVATE(self);
466     priv->load_cb = load_cb;
467     priv->load_cb_data = data;
468 }
469 
470 void
gnc_html_set_flyover_cb(GncHtml * self,GncHTMLFlyoverCB flyover_cb,gpointer data)471 gnc_html_set_flyover_cb( GncHtml* self, GncHTMLFlyoverCB flyover_cb, gpointer data )
472 {
473     GncHtmlPrivate* priv;
474 
475     g_return_if_fail( self != NULL );
476     g_return_if_fail( GNC_IS_HTML(self) );
477 
478     priv = GNC_HTML_GET_PRIVATE(self);
479     priv->flyover_cb = flyover_cb;
480     priv->flyover_cb_data = data;
481 }
482 
483 void
gnc_html_set_button_cb(GncHtml * self,GncHTMLButtonCB button_cb,gpointer data)484 gnc_html_set_button_cb( GncHtml* self, GncHTMLButtonCB button_cb, gpointer data )
485 {
486     GncHtmlPrivate* priv;
487 
488     g_return_if_fail( self != NULL );
489     g_return_if_fail( GNC_IS_HTML(self) );
490 
491     priv = GNC_HTML_GET_PRIVATE(self);
492     priv->button_cb = button_cb;
493     priv->button_cb_data = data;
494 }
495 
496 void
gnc_html_copy_to_clipboard(GncHtml * self)497 gnc_html_copy_to_clipboard( GncHtml* self )
498 {
499     g_return_if_fail( self != NULL );
500     g_return_if_fail( GNC_IS_HTML(self) );
501 
502     if ( GNC_HTML_GET_CLASS(self)->copy_to_clipboard != NULL )
503     {
504         GNC_HTML_GET_CLASS(self)->copy_to_clipboard( self );
505     }
506     else
507     {
508         DEBUG( "'copy_to_clipboard' not implemented" );
509     }
510 }
511 
512 /**************************************************************
513  * gnc_html_export_to_file : wrapper around the builtin function in gtkhtml
514  **************************************************************/
515 
516 gboolean
gnc_html_export_to_file(GncHtml * self,const gchar * filepath)517 gnc_html_export_to_file( GncHtml* self, const gchar* filepath )
518 {
519     g_return_val_if_fail( self != NULL, FALSE );
520     g_return_val_if_fail( GNC_IS_HTML(self), FALSE );
521 
522     if ( GNC_HTML_GET_CLASS(self)->export_to_file != NULL )
523     {
524         return GNC_HTML_GET_CLASS(self)->export_to_file( self, filepath );
525     }
526     else
527     {
528         DEBUG( "'export_to_file' not implemented" );
529         return FALSE;
530     }
531 }
532 #ifdef WEBKIT1
533 void
gnc_html_print(GncHtml * self,const char * jobname,gboolean export_pdf)534 gnc_html_print (GncHtml* self, const char *jobname, gboolean export_pdf)
535 #else
536 void
537 gnc_html_print (GncHtml* self, const char *jobname)
538 #endif
539 {
540     g_return_if_fail( self != NULL );
541      g_return_if_fail( jobname != NULL );
542     g_return_if_fail( GNC_IS_HTML(self) );
543 
544     if ( GNC_HTML_GET_CLASS(self)->print != NULL )
545     {
546 #ifdef WEBKIT1
547       GNC_HTML_GET_CLASS(self)->print (self, jobname, export_pdf);
548 #else
549         GNC_HTML_GET_CLASS(self)->print (self, jobname);
550 #endif
551     }
552     else
553     {
554         DEBUG( "'print' not implemented" );
555     }
556 }
557 
558 gnc_html_history *
gnc_html_get_history(GncHtml * self)559 gnc_html_get_history( GncHtml* self )
560 {
561     g_return_val_if_fail( self != NULL, NULL );
562     g_return_val_if_fail( GNC_IS_HTML(self), NULL );
563 
564     return GNC_HTML_GET_PRIVATE(self)->history;
565 }
566 
567 
568 GtkWidget *
gnc_html_get_widget(GncHtml * self)569 gnc_html_get_widget( GncHtml* self )
570 {
571     g_return_val_if_fail( self != NULL, NULL );
572     g_return_val_if_fail( GNC_IS_HTML(self), NULL );
573 
574     return GNC_HTML_GET_PRIVATE(self)->container;
575 }
576 
577 
578 GtkWidget *
gnc_html_get_webview(GncHtml * self)579 gnc_html_get_webview( GncHtml* self )
580 {
581     GncHtmlPrivate* priv;
582     GList *sw_list = NULL;
583     GtkWidget *webview = NULL;
584 
585     g_return_val_if_fail (self != NULL, NULL);
586     g_return_val_if_fail (GNC_IS_HTML(self), NULL);
587 
588     priv = GNC_HTML_GET_PRIVATE(self);
589     sw_list = gtk_container_get_children (GTK_CONTAINER(priv->container));
590 
591     if (sw_list) // the scroll window has only one child
592     {
593 #ifdef WEBKIT1
594         webview = sw_list->data;
595 #else
596         GList *vp_list = gtk_container_get_children (GTK_CONTAINER(sw_list->data));
597 
598         if (vp_list) // the viewport has only one child
599         {
600             webview = vp_list->data;
601             g_list_free (vp_list);
602         }
603 #endif
604     }
605     g_list_free (sw_list);
606     return webview;
607 }
608 
609 
610 void
gnc_html_set_parent(GncHtml * self,GtkWindow * parent)611 gnc_html_set_parent( GncHtml* self, GtkWindow* parent )
612 {
613     g_return_if_fail( self != NULL );
614     g_return_if_fail( GNC_IS_HTML(self) );
615 
616     if ( GNC_HTML_GET_CLASS(self)->set_parent != NULL )
617     {
618         GNC_HTML_GET_CLASS(self)->set_parent( self, parent );
619     }
620     else
621     {
622         DEBUG( "'set_parent' not implemented" );
623     }
624 }
625 
626 /* Register the URLType if it doesn't already exist.
627  * Returns TRUE if successful, FALSE if the type already exists.
628  */
629 gboolean
gnc_html_register_urltype(URLType type,const char * protocol)630 gnc_html_register_urltype( URLType type, const char *protocol )
631 {
632     URLType  lc_type  = NULL;
633     char    *lc_proto = NULL;
634 
635     if (!gnc_html_type_to_proto_hash)
636     {
637         gnc_html_type_to_proto_hash = g_hash_table_new (g_str_hash, g_str_equal);
638         gnc_html_proto_to_type_hash = g_hash_table_new (g_str_hash, g_str_equal);
639     }
640     if (!protocol) return FALSE;
641 
642     lc_type = g_ascii_strdown (type, -1);
643     if (g_hash_table_lookup (gnc_html_type_to_proto_hash, lc_type))
644     {
645         g_free (lc_type);
646         return FALSE;
647     }
648 
649     lc_proto = g_ascii_strdown (protocol, -1);
650     g_hash_table_insert (gnc_html_type_to_proto_hash, lc_type, (gpointer)lc_proto);
651     if (*lc_proto)
652         g_hash_table_insert (gnc_html_proto_to_type_hash, (gpointer)lc_proto, lc_type);
653 
654     return TRUE;
655 }
656 
657 void
gnc_html_initialize(void)658 gnc_html_initialize( void )
659 {
660     int i;
661     static struct
662     {
663         URLType	type;
664         char *	protocol;
665     } types[] =
666     {
667         { URL_TYPE_FILE, "file" },
668         { URL_TYPE_JUMP, "" },
669         { URL_TYPE_HTTP, "http" },
670         { URL_TYPE_FTP, "ftp" },
671         { URL_TYPE_SECURE, "https" },
672         { URL_TYPE_REGISTER, "gnc-register" },
673         { URL_TYPE_ACCTTREE, "gnc-acct-tree" },
674         { URL_TYPE_REPORT, "gnc-report" },
675         { URL_TYPE_OPTIONS, "gnc-options" },
676         { URL_TYPE_SCHEME, "gnc-scm" },
677         { URL_TYPE_HELP, "gnc-help" },
678         { URL_TYPE_XMLDATA, "gnc-xml" },
679         { URL_TYPE_PRICE, "gnc-price" },
680         { URL_TYPE_BUDGET, "gnc-budget" },
681         { URL_TYPE_OTHER, "" },
682         { NULL, NULL }
683     };
684 
685     for (i = 0; types[i].type; i++)
686         gnc_html_register_urltype (types[i].type, types[i].protocol);
687 }
688 
689 /**
690  * Creates a new HMTL url.
691  *
692  * @param type URL type
693  * @param location URL location
694  * @param label URL label (optional)
695  * @return Newly created URL.  This string must be freed by the caller.
696  */
697 gchar*
gnc_build_url(URLType type,const gchar * location,const gchar * label)698 gnc_build_url( URLType type, const gchar* location, const gchar* label )
699 {
700     URLType  lc_type  = NULL;
701     char * type_name;
702 
703     DEBUG(" ");
704     lc_type = g_ascii_strdown (type, -1);
705     type_name = g_hash_table_lookup (gnc_html_type_to_proto_hash, lc_type);
706     g_free (lc_type);
707     if (!type_name)
708         type_name = "";
709 
710     if (label)
711     {
712         return g_strdup_printf("%s%s%s#%s", type_name, (*type_name ? ":" : ""),
713                                (location ? location : ""),
714                                label ? label : "");
715     }
716     else
717     {
718         return g_strdup_printf("%s%s%s", type_name, (*type_name ? ":" : ""),
719                                (location ? location : ""));
720     }
721 }
722 
723 /********************************************************************
724  * gnc_html_encode_string
725  * RFC 1738 encoding of string for submission with an HTML form.
726  * GPL code lifted from gtkhtml.  copyright notice:
727  *
728  * Copyright (C) 1997 Martin Jones (mjones@kde.org)
729  * Copyright (C) 1997 Torben Weis (weis@kde.org)
730  * Copyright (C) 1999 Helix Code, Inc.
731  ********************************************************************/
732 
733 char *
gnc_html_encode_string(const char * str)734 gnc_html_encode_string(const char * str)
735 {
736     static gchar *safe = "$-._!*(),"; /* RFC 1738 */
737     unsigned pos      = 0;
738     GString *encoded  = g_string_new ("");
739     gchar buffer[5], *ptr;
740     guchar c;
741 
742     if (!str) return NULL;
743 
744     while (pos < strlen(str))
745     {
746         c = (unsigned char) str[pos];
747 
748         if ((( c >= 'A') && ( c <= 'Z')) ||
749                 (( c >= 'a') && ( c <= 'z')) ||
750                 (( c >= '0') && ( c <= '9')) ||
751                 (strchr(safe, c)))
752         {
753             encoded = g_string_append_c (encoded, c);
754         }
755         else if ( c == ' ' )
756         {
757             encoded = g_string_append_c (encoded, '+');
758         }
759         else if ( c == '\n' )
760         {
761             encoded = g_string_append (encoded, "%0D%0A");
762         }
763         else if ( c != '\r' )
764         {
765             sprintf( buffer, "%%%02X", (int)c );
766             encoded = g_string_append (encoded, buffer);
767         }
768         pos++;
769     }
770 
771     ptr = encoded->str;
772 
773     g_string_free (encoded, FALSE);
774 
775     return (char *)ptr;
776 }
777 
778 
779 char *
gnc_html_decode_string(const char * str)780 gnc_html_decode_string(const char * str)
781 {
782     static gchar * safe = "$-._!*(),"; /* RFC 1738 */
783     GString * decoded  = g_string_new ("");
784     const gchar   * ptr;
785     guchar  c;
786     guint   hexval;
787     ptr = str;
788 
789     if (!str) return NULL;
790 
791     while (*ptr)
792     {
793         c = (unsigned char) * ptr;
794         if ((( c >= 'A') && ( c <= 'Z')) ||
795                 (( c >= 'a') && ( c <= 'z')) ||
796                 (( c >= '0') && ( c <= '9')) ||
797                 (strchr(safe, c)))
798         {
799             decoded = g_string_append_c (decoded, c);
800         }
801         else if ( c == '+' )
802         {
803             decoded = g_string_append_c (decoded, ' ');
804         }
805         else if (!strncmp(ptr, "%0D0A", 5))
806         {
807             decoded = g_string_append (decoded, "\n");
808             ptr += 4;
809         }
810         else if (c == '%')
811         {
812             ptr++;
813             if (1 == sscanf(ptr, "%02X", &hexval))
814                 decoded = g_string_append_c(decoded, (char)hexval);
815             else
816                 decoded = g_string_append_c(decoded, ' ');
817             ptr++;
818         }
819         ptr++;
820     }
821     ptr = decoded->str;
822     g_string_free (decoded, FALSE);
823 
824     return (char *)ptr;
825 }
826 
827 /********************************************************************
828  * escape/unescape_newlines : very simple string encoding for GPG
829  * ASCII-armored text.
830  ********************************************************************/
831 
832 char *
gnc_html_unescape_newlines(const gchar * in)833 gnc_html_unescape_newlines(const gchar * in)
834 {
835     const char * ip = in;
836     char    * cstr = NULL;
837     GString * rv = g_string_new("");
838 
839     for (ip = in; *ip; ip++)
840     {
841         if ((*ip == '\\') && (*(ip + 1) == 'n'))
842         {
843             g_string_append(rv, "\n");
844             ip++;
845         }
846         else
847         {
848             g_string_append_c(rv, *ip);
849         }
850     }
851 
852     g_string_append_c(rv, 0);
853     cstr = rv->str;
854     g_string_free(rv, FALSE);
855     return cstr;
856 }
857 
858 char *
gnc_html_escape_newlines(const gchar * in)859 gnc_html_escape_newlines(const gchar * in)
860 {
861     char *out;
862     const char * ip   = in;
863     GString * escaped = g_string_new("");
864 
865     for (ip = in; *ip; ip++)
866     {
867         if (*ip == '\012')
868         {
869             g_string_append(escaped, "\\n");
870         }
871         else
872         {
873             g_string_append_c(escaped, *ip);
874         }
875     }
876     g_string_append_c(escaped, 0);
877     out = escaped->str;
878     g_string_free(escaped, FALSE);
879     return out;
880 }
881 
882 void
gnc_html_register_object_handler(const char * classid,GncHTMLObjectCB hand)883 gnc_html_register_object_handler( const char * classid,
884                                   GncHTMLObjectCB hand )
885 {
886     g_return_if_fail( classid != NULL );
887 
888     if ( gnc_html_object_handlers == NULL )
889     {
890         gnc_html_object_handlers = g_hash_table_new( g_str_hash, g_str_equal );
891     }
892 
893     gnc_html_unregister_object_handler( classid );
894     if ( hand != NULL )
895     {
896         gchar *lc_id  = g_ascii_strdown (classid, -1);
897         g_hash_table_insert( gnc_html_object_handlers, lc_id, hand );
898     }
899 }
900 
901 void
gnc_html_unregister_object_handler(const gchar * classid)902 gnc_html_unregister_object_handler( const gchar* classid )
903 {
904     gchar* keyptr = NULL;
905     gchar* valptr = NULL;
906     gchar** p_keyptr = &keyptr;
907     gchar** p_valptr = &valptr;
908     gchar* lc_id = g_ascii_strdown (classid, -1);
909 
910     if ( g_hash_table_lookup_extended( gnc_html_object_handlers,
911                                        lc_id,
912                                        (gpointer *)p_keyptr,
913                                        (gpointer *)p_valptr) )
914     {
915         g_hash_table_remove( gnc_html_object_handlers, lc_id );
916         g_free( keyptr );
917     }
918     g_free( lc_id );
919 }
920 
921 void
gnc_html_register_stream_handler(URLType url_type,GncHTMLStreamCB hand)922 gnc_html_register_stream_handler( URLType url_type, GncHTMLStreamCB hand )
923 {
924     g_return_if_fail( url_type != NULL && *url_type != '\0' );
925 
926     if ( gnc_html_stream_handlers == NULL )
927     {
928         gnc_html_stream_handlers = g_hash_table_new( g_str_hash, g_str_equal );
929     }
930 
931     gnc_html_unregister_stream_handler( url_type );
932     if ( hand != NULL )
933     {
934         URLType  lc_type  = g_ascii_strdown (url_type, -1);
935         g_hash_table_insert( gnc_html_stream_handlers, lc_type, hand );
936     }
937 }
938 
939 void
gnc_html_unregister_stream_handler(URLType url_type)940 gnc_html_unregister_stream_handler( URLType url_type )
941 {
942     URLType  lc_type = g_ascii_strdown (url_type, -1);
943     g_hash_table_remove( gnc_html_stream_handlers, lc_type );
944     g_free(lc_type);
945 }
946 
947 void
gnc_html_register_url_handler(URLType url_type,GncHTMLUrlCB hand)948 gnc_html_register_url_handler( URLType url_type, GncHTMLUrlCB hand )
949 {
950     g_return_if_fail( url_type != NULL && *url_type != '\0' );
951 
952     if ( gnc_html_url_handlers == NULL )
953     {
954         gnc_html_url_handlers = g_hash_table_new( g_str_hash, g_str_equal );
955     }
956 
957     gnc_html_unregister_url_handler( url_type );
958     if ( hand != NULL )
959     {
960         URLType lc_type = g_ascii_strdown (url_type, -1);
961         g_hash_table_insert( gnc_html_url_handlers, lc_type, hand );
962     }
963 }
964 
965 void
gnc_html_unregister_url_handler(URLType url_type)966 gnc_html_unregister_url_handler( URLType url_type )
967 {
968     URLType lc_type = g_ascii_strdown (url_type, -1);
969     g_hash_table_remove( gnc_html_url_handlers, lc_type );
970     g_free(lc_type);
971 }
972