1 /********************************************************************
2  * gnc-html-webkit.c -- gnucash report renderer using webkit        *
3  *                                                                  *
4  * Copyright (C) 2000 Bill Gribble <grib@billgribble.com>           *
5  * Copyright (C) 2001 Linas Vepstas <linas@linas.org>               *
6  * Copyright (C) 2009 Phil Longstaff <plongstaff@rogers.com>        *
7  *                                                                  *
8  * This program is free software; you can redistribute it and/or    *
9  * modify it under the terms of the GNU General Public License as   *
10  * published by the Free Software Foundation; either version 2 of   *
11  * the License, or (at your option) any later version.              *
12  *                                                                  *
13  * This program is distributed in the hope that it will be useful,  *
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
16  * GNU General Public License for more details.                     *
17  *                                                                  *
18  * You should have received a copy of the GNU General Public License*
19  * along with this program; if not, contact:                        *
20  *                                                                  *
21  * Free Software Foundation           Voice:  +1-617-542-5942       *
22  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
23  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
24  ********************************************************************/
25 
26 #include <config.h>
27 
28 #include <platform.h>
29 #ifdef __MINGW32__
30 #define _GL_UNISTD_H //Deflect poisonous define of close in Guile's GnuLib
31 #endif
32 #include <libguile.h>
33 #if PLATFORM(WINDOWS)
34 #include <windows.h>
35 #endif
36 
37 #include <gtk/gtk.h>
38 #include <glib/gi18n.h>
39 #include <glib/gstdio.h>
40 #include <sys/types.h>
41 #include <sys/stat.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <errno.h>
45 #include <fcntl.h>
46 #include <unistd.h>
47 #include <regex.h>
48 
49 #include <webkit2/webkit2.h>
50 
51 #include "Account.h"
52 #include "gnc-prefs.h"
53 #include "gnc-gui-query.h"
54 #include "gnc-engine.h"
55 #include "gnc-html.h"
56 #include "gnc-html-webkit.h"
57 #include "gnc-html-history.h"
58 #include "print-session.h"
59 #include "gnc-state.h"
60 #include "print-session.h"
61 
62 
63 G_DEFINE_TYPE(GncHtmlWebkit, gnc_html_webkit, GNC_TYPE_HTML )
64 
65 static void gnc_html_webkit_dispose( GObject* obj );
66 static void gnc_html_webkit_finalize( GObject* obj );
67 static void gnc_html_webkit_class_init( GncHtmlWebkitClass* klass );
68 static void gnc_html_webkit_init( GncHtmlWebkit* gs );
69 
70 #define GNC_HTML_WEBKIT_GET_PRIVATE(o) (GNC_HTML_WEBKIT(o)->priv)
71 
72 #include "gnc-html-webkit-p.h"
73 
74 /* indicates the debugging module that this .o belongs to.  */
75 static QofLogModule log_module = GNC_MOD_HTML;
76 
77 /* hashes for URLType -> protocol and protocol -> URLType */
78 //extern GHashTable* gnc_html_type_to_proto_hash;
79 //extern GHashTable* gnc_html_proto_to_type_hash;
80 
81 /* hashes an HTML <object classid="ID"> classid to a handler function */
82 extern GHashTable* gnc_html_object_handlers;
83 
84 /* hashes handlers for loading different URLType data */
85 extern GHashTable* gnc_html_stream_handlers;
86 
87 /* hashes handlers for handling different URLType data */
88 extern GHashTable* gnc_html_url_handlers;
89 
90 static char error_404_format[] = "<html><body><h3>%s</h3><p>%s</body></html>";
91 static char error_404_title[] = N_("Not found");
92 static char error_404_body[] = N_("The specified URL could not be loaded.");
93 
94 #define BASE_URI_NAME "base-uri"
95 #define GNC_PREF_RPT_DFLT_ZOOM "default-zoom"
96 
97 static gboolean webkit_decide_policy_cb (WebKitWebView* web_view,
98                      WebKitPolicyDecision *decision,
99                      WebKitPolicyDecisionType decision_type,
100                      gpointer user_data);
101 static void webkit_mouse_target_cb (WebKitWebView* web_view,
102                      WebKitHitTestResult *hit,
103                      guint modifiers, gpointer data);
104 static gboolean webkit_notification_cb (WebKitWebView *web_view,
105                      WebKitNotification *note,
106                      gpointer user_data);
107 static gboolean webkit_load_failed_cb (WebKitWebView *web_view,
108                      WebKitLoadEvent event,
109                      gchar *uri, GError *error,
110                      gpointer user_data);
111 static void webkit_resource_load_started_cb (WebKitWebView *web_view,
112                                              WebKitWebResource *resource,
113                                              WebKitURIRequest *request,
114                                              gpointer data);
115 static gchar* handle_embedded_object( GncHtmlWebkit* self, gchar* html_str );
116 static void impl_webkit_show_url( GncHtml* self, URLType type,
117                                   const gchar* location, const gchar* label,
118                                   gboolean new_window_hint );
119 static void impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen );
120 static void impl_webkit_reload( GncHtml* self, gboolean force_rebuild );
121 static void impl_webkit_copy_to_clipboard( GncHtml* self );
122 static gboolean impl_webkit_export_to_file( GncHtml* self, const gchar* filepath );
123 static void impl_webkit_print (GncHtml* self,const gchar* jobname);
124 static void impl_webkit_cancel( GncHtml* self );
125 static void impl_webkit_set_parent( GncHtml* self, GtkWindow* parent );
126 static void impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data);
127 
128 static GtkWidget*
gnc_html_webkit_webview_new(void)129 gnc_html_webkit_webview_new (void)
130 {
131      GtkWidget *view = webkit_web_view_new ();
132      WebKitSettings *webkit_settings = NULL;
133      const char *default_font_family = NULL;
134      GtkStyleContext *style = gtk_widget_get_style_context (view);
135      GValue val = G_VALUE_INIT;
136      GtkStateFlags state = gtk_style_context_get_state (style);
137      gtk_style_context_get_property (style, GTK_STYLE_PROPERTY_FONT,
138                      state, &val);
139 
140      if (G_VALUE_HOLDS_BOXED (&val))
141      {
142       const PangoFontDescription *font =
143            (const PangoFontDescription*)g_value_get_boxed (&val);
144       default_font_family = pango_font_description_get_family (font);
145      }
146 /* Set default webkit settings */
147      webkit_settings = webkit_web_view_get_settings (WEBKIT_WEB_VIEW (view));
148      g_object_set (G_OBJECT(webkit_settings),
149                    "default-charset", "utf-8",
150                    "allow-file-access-from-file-urls", TRUE,
151                    "allow-universal-access-from-file-urls", TRUE,
152                    "enable-java", FALSE,
153                    "enable-page-cache", FALSE,
154                    "enable-plugins", FALSE,
155                    "enable-site-specific-quirks", FALSE,
156                    "enable-xss-auditor", FALSE,
157                    "enable-developer-extras", TRUE,
158                    NULL);
159      if (default_font_family != NULL)
160      {
161           g_object_set (G_OBJECT (webkit_settings),
162               "default-font-family", default_font_family, NULL);
163      }
164      g_value_unset (&val);
165      return view;
166 }
167 
168 static void
gnc_html_webkit_init(GncHtmlWebkit * self)169 gnc_html_webkit_init( GncHtmlWebkit* self )
170 {
171      GncHtmlWebkitPrivate* priv;
172      GncHtmlWebkitPrivate* new_priv;
173      gdouble zoom = 1.0;
174 
175      new_priv = g_realloc (GNC_HTML(self)->priv, sizeof(GncHtmlWebkitPrivate));
176      priv = self->priv = new_priv;
177      GNC_HTML(self)->priv = (GncHtmlPrivate*)priv;
178 
179      priv->html_string = NULL;
180      priv->web_view = WEBKIT_WEB_VIEW (gnc_html_webkit_webview_new ());
181 
182 
183      /* Scale everything up */
184      zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT,
185                  GNC_PREF_RPT_DFLT_ZOOM);
186      webkit_web_view_set_zoom_level (priv->web_view, zoom);
187 
188 
189      gtk_container_add( GTK_CONTAINER(priv->base.container),
190                         GTK_WIDGET(priv->web_view) );
191 
192      g_object_ref_sink( priv->base.container );
193 
194      /* signals */
195      g_signal_connect (priv->web_view, "decide-policy",
196                        G_CALLBACK (webkit_decide_policy_cb),
197                        self);
198 
199      g_signal_connect (priv->web_view, "mouse-target-changed",
200                        G_CALLBACK (webkit_mouse_target_cb),
201                        self);
202 
203      g_signal_connect (priv->web_view, "show-notification",
204                        G_CALLBACK (webkit_notification_cb),
205                        self);
206 
207      g_signal_connect (priv->web_view, "load-failed",
208                        G_CALLBACK (webkit_load_failed_cb),
209                        self);
210      g_signal_connect (priv->web_view, "resource-load-started",
211                        G_CALLBACK (webkit_resource_load_started_cb),
212                        self);
213      gnc_prefs_register_cb (GNC_PREFS_GROUP_GENERAL_REPORT,
214                             GNC_PREF_RPT_DFLT_ZOOM,
215                             impl_webkit_default_zoom_changed,
216                             self);
217 
218      LEAVE("retval %p", self);
219 }
220 
221 static void
gnc_html_webkit_class_init(GncHtmlWebkitClass * klass)222 gnc_html_webkit_class_init( GncHtmlWebkitClass* klass )
223 {
224      GObjectClass* gobject_class = G_OBJECT_CLASS(klass);
225      GncHtmlClass* html_class = GNC_HTML_CLASS(klass);
226 
227      gobject_class->dispose = gnc_html_webkit_dispose;
228      gobject_class->finalize = gnc_html_webkit_finalize;
229 
230      html_class->show_url = impl_webkit_show_url;
231      html_class->show_data = impl_webkit_show_data;
232      html_class->reload = impl_webkit_reload;
233      html_class->copy_to_clipboard = impl_webkit_copy_to_clipboard;
234      html_class->export_to_file = impl_webkit_export_to_file;
235      html_class->print = impl_webkit_print;
236      html_class->cancel = impl_webkit_cancel;
237      html_class->set_parent = impl_webkit_set_parent;
238 }
239 
240 static void
gnc_html_webkit_dispose(GObject * obj)241 gnc_html_webkit_dispose( GObject* obj )
242 {
243      GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
244      GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
245 
246      if ( priv->web_view != NULL )
247      {
248           gtk_container_remove (GTK_CONTAINER(priv->base.container),
249                                 GTK_WIDGET(priv->web_view));
250 
251           priv->web_view = NULL;
252      }
253 
254      if ( priv->html_string != NULL )
255      {
256           g_free( priv->html_string );
257           priv->html_string = NULL;
258      }
259 
260      gnc_prefs_remove_cb_by_func (GNC_PREFS_GROUP_GENERAL_REPORT,
261                                   GNC_PREF_RPT_DFLT_ZOOM,
262                                   impl_webkit_default_zoom_changed,
263                                   obj);
264 
265      G_OBJECT_CLASS(gnc_html_webkit_parent_class)->dispose( obj );
266 }
267 
268 static void
gnc_html_webkit_finalize(GObject * obj)269 gnc_html_webkit_finalize( GObject* obj )
270 {
271      GncHtmlWebkit* self = GNC_HTML_WEBKIT(obj);
272 
273 //      if( self->priv != NULL ) {
274 //              g_free( self->priv );
275      self->priv = NULL;
276 //      }
277 
278      G_OBJECT_CLASS(gnc_html_webkit_parent_class)->finalize( obj );
279 }
280 
281 /*****************************************************************************/
282 
283 static char*
extract_base_name(URLType type,const gchar * path)284 extract_base_name(URLType type, const gchar* path)
285 {
286      gchar       machine_rexp[] = "^(//[^/]*)/*(/.*)?$";
287      gchar       path_rexp[] = "^/*(.*)/+([^/]*)$";
288      regex_t     compiled_m, compiled_p;
289      regmatch_t  match[4];
290      gchar       * machine = NULL, * location = NULL, * base = NULL;
291      gchar       * basename = NULL;
292 
293      DEBUG(" ");
294      if (!path) return NULL;
295 
296      regcomp(&compiled_m, machine_rexp, REG_EXTENDED);
297      regcomp(&compiled_p, path_rexp, REG_EXTENDED);
298 
299      if (!g_strcmp0 (type, URL_TYPE_HTTP) ||
300          !g_strcmp0 (type, URL_TYPE_SECURE) ||
301          !g_strcmp0 (type, URL_TYPE_FTP))
302      {
303 
304           /* step 1: split the machine name away from the path
305            * components */
306           if (!regexec(&compiled_m, path, 4, match, 0))
307           {
308                /* $1 is the machine name */
309                if (match[1].rm_so != -1)
310                {
311                     machine = g_strndup(path + match[1].rm_so,
312                                         match[1].rm_eo - match[1].rm_so);
313                }
314                /* $2 is the path */
315                if (match[2].rm_so != -1)
316                {
317                     location = g_strndup(path + match[2].rm_so,
318                                          match[2].rm_eo - match[2].rm_so);
319                }
320           }
321      }
322      else
323      {
324           location = g_strdup(path);
325      }
326      /* step 2: split up the path into prefix and file components */
327      if (location)
328      {
329           if (!regexec(&compiled_p, location, 4, match, 0))
330           {
331                if (match[1].rm_so != -1)
332                {
333                     base = g_strndup(location + match[1].rm_so,
334                                      match[1].rm_eo - match[1].rm_so);
335                }
336                else
337                {
338                     base = NULL;
339                }
340           }
341      }
342 
343      regfree(&compiled_m);
344      regfree(&compiled_p);
345 
346      if (machine)
347      {
348           if (base && (strlen(base) > 0))
349           {
350                basename = g_strconcat(machine, "/", base, "/", NULL);
351           }
352           else
353           {
354                basename = g_strconcat(machine, "/", NULL);
355           }
356      }
357      else
358      {
359           if (base && (strlen(base) > 0))
360           {
361                basename = g_strdup(base);
362           }
363           else
364           {
365                basename = NULL;
366           }
367      }
368 
369      g_free(machine);
370      g_free(base);
371      g_free(location);
372      return basename;
373 }
374 
375 static gboolean
http_allowed()376 http_allowed()
377 {
378      return TRUE;
379 }
380 
381 static gboolean
https_allowed()382 https_allowed()
383 {
384      return TRUE;
385 }
386 
387 static gchar*
handle_embedded_object(GncHtmlWebkit * self,gchar * html_str)388 handle_embedded_object( GncHtmlWebkit* self, gchar* html_str )
389 {
390      // Find the <object> tag and get the classid from it.  This will provide the correct
391      // object callback handler.  Pass the <object> entity text to the handler.  What should
392      // come back is embedded image information.
393      gchar* remainder_str = html_str;
394      gchar* object_tag;
395      gchar* end_object_tag;
396      gchar* object_contents;
397      gchar* html_str_start = NULL;
398      gchar* html_str_middle;
399      gchar* html_str_result = NULL;
400      gchar* classid_start;
401      gchar* classid_end;
402      gchar* classid_str;
403      gchar* new_chunk;
404      GncHTMLObjectCB h;
405 
406      object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
407      while (object_tag)
408      {
409 
410           classid_start = object_tag + strlen( "<object classid=" ) + 1;
411           classid_end = g_strstr_len( classid_start, -1, "\"" );
412           classid_str = g_strndup( classid_start, (classid_end - classid_start) );
413 
414           end_object_tag = g_strstr_len( object_tag, -1, "</object>" );
415           if ( end_object_tag == NULL )
416           {
417                /*  Hmmm... no object end tag
418                    Return the original html string because we can't properly parse it */
419                g_free (classid_str);
420                g_free (html_str_result);
421                return g_strdup (html_str);
422           }
423           end_object_tag += strlen( "</object>" );
424           object_contents = g_strndup( object_tag, (end_object_tag - object_tag) );
425 
426           h = g_hash_table_lookup( gnc_html_object_handlers, classid_str );
427           if ( h != NULL )
428           {
429                (void)h( GNC_HTML(self), object_contents, &html_str_middle );
430           }
431           else
432           {
433                html_str_middle = g_strdup_printf( "No handler found for classid \"%s\"", classid_str );
434           }
435 
436           html_str_start = html_str_result;
437           new_chunk = g_strndup (remainder_str, (object_tag - remainder_str));
438           if (!html_str_start)
439                html_str_result = g_strconcat (new_chunk, html_str_middle, NULL);
440           else
441                html_str_result = g_strconcat (html_str_start, new_chunk, html_str_middle, NULL);
442 
443           g_free( html_str_start );
444           g_free( new_chunk );
445           g_free( html_str_middle );
446 
447           remainder_str = end_object_tag;
448           object_tag = g_strstr_len( remainder_str, -1, "<object classid=" );
449      }
450 
451      if (html_str_result)
452      {
453           html_str_start =  html_str_result;
454           html_str_result = g_strconcat (html_str_start, remainder_str, NULL);
455           g_free (html_str_start);
456      }
457      else
458           html_str_result = g_strdup (remainder_str);
459 
460      return html_str_result;
461 }
462 
463 /********************************************************************
464  * load_to_stream : actually do the work of loading the HTML
465  * or binary data referenced by a URL and feeding it into the webkit
466  * widget.
467  ********************************************************************/
468 
469 static void
load_to_stream(GncHtmlWebkit * self,URLType type,const gchar * location,const gchar * label)470 load_to_stream( GncHtmlWebkit* self, URLType type,
471                 const gchar* location, const gchar* label )
472 {
473      gchar* fdata = NULL;
474      int fdata_len = 0;
475      GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
476 
477      DEBUG( "type %s, location %s, label %s", type ? type : "(null)",
478             location ? location : "(null)", label ? label : "(null)");
479 
480      g_return_if_fail( self != NULL );
481 
482      if ( gnc_html_stream_handlers != NULL )
483      {
484           GncHTMLStreamCB stream_handler;
485 
486           stream_handler = g_hash_table_lookup( gnc_html_stream_handlers, type );
487           if ( stream_handler )
488           {
489                gboolean ok = stream_handler( location, &fdata, &fdata_len );
490 
491                if ( ok )
492                {
493                     fdata = fdata ? fdata : g_strdup( "" );
494 
495                     // Until webkitgtk supports download requests,
496                     // look for "<object classid=" indicating the
497                     // beginning of an embedded graph.  If found,
498                     // handle it
499                     if ( g_strstr_len( fdata, -1, "<object classid=" ) != NULL )
500                     {
501                          gchar* new_fdata;
502                          new_fdata = handle_embedded_object( self, fdata );
503                          g_free( fdata );
504                          fdata = new_fdata;
505                     }
506 
507                     // Save a copy for export purposes
508                     if ( priv->html_string != NULL )
509                     {
510                          g_free( priv->html_string );
511                     }
512                     priv->html_string = g_strdup( fdata );
513                     impl_webkit_show_data( GNC_HTML(self), fdata, strlen(fdata) );
514 //                webkit_web_view_load_html (priv->web_view, fdata,
515 //                                           BASE_URI_NAME);
516                }
517                else
518                {
519                     fdata = fdata ? fdata :
520                          g_strdup_printf( error_404_format,
521                                           _(error_404_title), _(error_404_body) );
522                     webkit_web_view_load_html (priv->web_view, fdata,
523                            BASE_URI_NAME);
524                }
525 
526                g_free( fdata );
527 
528                if ( label )
529                {
530                     while ( gtk_events_pending() )
531                     {
532                          gtk_main_iteration();
533                     }
534                     /* No action required: Webkit jumps to the anchor on its own. */
535                }
536                return;
537           }
538      }
539 
540      do
541      {
542           if ( !g_strcmp0( type, URL_TYPE_SECURE ) ||
543                !g_strcmp0( type, URL_TYPE_HTTP ) )
544           {
545 
546                if ( !g_strcmp0( type, URL_TYPE_SECURE ) )
547                {
548                     if ( !https_allowed() )
549                     {
550                         gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
551                                            _("Secure HTTP access is disabled. "
552                                              "You can enable it in the Network section of "
553                                              "the Preferences dialog."));
554                          break;
555                     }
556                }
557 
558                if ( !http_allowed() )
559                {
560                    gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
561                                       _("Network HTTP access is disabled. "
562                                         "You can enable it in the Network section of "
563                                         "the Preferences dialog."));
564                }
565                else
566                {
567                     gnc_build_url( type, location, label );
568                }
569           }
570           else
571           {
572                PWARN( "load_to_stream for inappropriate type\n"
573                       "\turl = '%s#%s'\n",
574                       location ? location : "(null)",
575                       label ? label : "(null)" );
576                fdata = g_strdup_printf( error_404_format,
577                                         _(error_404_title), _(error_404_body) );
578                webkit_web_view_load_html (priv->web_view, fdata, BASE_URI_NAME);
579                g_free( fdata );
580           }
581      }
582      while ( FALSE );
583 }
584 static gboolean
perform_navigation_policy(WebKitWebView * web_view,WebKitNavigationPolicyDecision * decision,GncHtml * self)585 perform_navigation_policy (WebKitWebView *web_view,
586                WebKitNavigationPolicyDecision *decision,
587                GncHtml *self)
588 {
589      WebKitURIRequest *req = NULL;
590      const gchar* uri; // Can't init it here.
591      gchar *scheme = NULL, *location = NULL, *label = NULL;
592      gboolean ignore = FALSE;
593      WebKitNavigationAction *action =
594       webkit_navigation_policy_decision_get_navigation_action (decision);
595      if (webkit_navigation_action_get_navigation_type (action) !=
596          WEBKIT_NAVIGATION_TYPE_LINK_CLICKED)
597      {
598           webkit_policy_decision_use ((WebKitPolicyDecision*)decision);
599           return TRUE;
600      }
601      req = webkit_navigation_action_get_request (action);
602      uri = webkit_uri_request_get_uri (req);
603      scheme =  gnc_html_parse_url (self, uri, &location, &label);
604      if (strcmp (scheme, URL_TYPE_FILE) != 0)
605      {
606           impl_webkit_show_url (self, scheme, location, label, FALSE);
607           ignore = TRUE;
608      }
609      g_free (location);
610      g_free (label);
611      if (ignore)
612           webkit_policy_decision_ignore ((WebKitPolicyDecision*)decision);
613      else
614           webkit_policy_decision_use ((WebKitPolicyDecision*)decision);
615      return TRUE;
616 }
617 
618 static gboolean
webkit_decide_policy_cb(WebKitWebView * web_view,WebKitPolicyDecision * decision,WebKitPolicyDecisionType decision_type,gpointer user_data)619 webkit_decide_policy_cb (WebKitWebView *web_view,
620              WebKitPolicyDecision *decision,
621              WebKitPolicyDecisionType decision_type,
622              gpointer user_data)
623 {
624 /* This turns out to be the signal to intercept for handling a link-click. */
625      if (decision_type != WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION)
626      {
627           webkit_policy_decision_use (decision);
628           return TRUE;
629      }
630      return perform_navigation_policy (
631       web_view, (WebKitNavigationPolicyDecision*) decision,
632       GNC_HTML (user_data));
633 }
634 
635 static void
webkit_mouse_target_cb(WebKitWebView * web_view,WebKitHitTestResult * hit,guint modifiers,gpointer user_data)636 webkit_mouse_target_cb (WebKitWebView *web_view, WebKitHitTestResult *hit,
637             guint modifiers, gpointer user_data)
638 {
639      GncHtmlWebkitPrivate* priv;
640      GncHtmlWebkit *self = (GncHtmlWebkit*)user_data;
641      gchar *uri;
642 
643      if (!webkit_hit_test_result_context_is_link (hit))
644          return;
645 
646      priv = GNC_HTML_WEBKIT_GET_PRIVATE (self);
647      uri = g_strdup (webkit_hit_test_result_get_link_uri (hit));
648      g_free (priv->base.current_link);
649      priv->base.current_link = uri;
650      if (priv->base.flyover_cb)
651      {
652           (priv->base.flyover_cb) (GNC_HTML (self), uri,
653                    priv->base.flyover_cb_data);
654      }
655 }
656 static gboolean
webkit_notification_cb(WebKitWebView * web_view,WebKitNotification * note,gpointer user_data)657 webkit_notification_cb (WebKitWebView* web_view, WebKitNotification *note,
658             gpointer user_data)
659 {
660      GtkWindow *top = NULL;
661      GtkWidget *dialog = NULL;
662      GncHtmlWebkit *self = (GncHtmlWebkit*)user_data;
663      g_return_val_if_fail (self != NULL, FALSE);
664      g_return_val_if_fail (note != NULL, FALSE);
665 
666      top = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (web_view)));
667      dialog = gtk_message_dialog_new (top, GTK_DIALOG_MODAL,
668                                       GTK_MESSAGE_WARNING, GTK_BUTTONS_CLOSE,
669                                       "%s\n%s",
670                                       webkit_notification_get_title (note),
671                                       webkit_notification_get_body (note));
672      gtk_dialog_run (GTK_DIALOG (dialog));
673      gtk_widget_destroy (dialog);
674      return TRUE;
675 }
676 
677 static gboolean
webkit_load_failed_cb(WebKitWebView * web_view,WebKitLoadEvent event,gchar * uri,GError * error,gpointer user_data)678 webkit_load_failed_cb (WebKitWebView *web_view, WebKitLoadEvent event,
679                        gchar *uri, GError *error, gpointer user_data)
680 {
681      PERR ("WebKit load of %s failed due to %s\n", uri, error->message);
682      return FALSE;
683 }
684 static void
webkit_resource_load_failed_cb(WebKitWebResource * resource,GError * error,gpointer data)685 webkit_resource_load_failed_cb (WebKitWebResource *resource,
686                                 GError *error,
687                                 gpointer data)
688 {
689      WebKitURIResponse *response = webkit_web_resource_get_response (resource);
690      const gchar * uri = webkit_web_resource_get_uri (resource);
691      PERR ("Load of resource at %s failed with error %s and status code %d.\n",
692            uri, error->message, webkit_uri_response_get_status_code (response));
693 }
694 
695 static void
webkit_resource_load_finished_cb(WebKitWebResource * resource,gpointer data)696 webkit_resource_load_finished_cb (WebKitWebResource *resource, gpointer data)
697 {
698      DEBUG ("Load of resource %s completed.\n", webkit_web_resource_get_uri(resource));
699 }
700 
701 static void
webkit_resource_load_started_cb(WebKitWebView * web_view,WebKitWebResource * resource,WebKitURIRequest * request,gpointer data)702 webkit_resource_load_started_cb (WebKitWebView *web_view,
703                                  WebKitWebResource *resource,
704                                  WebKitURIRequest *request,
705                                  gpointer data)
706 {
707      DEBUG ("Load of resource %s begun.\n", webkit_web_resource_get_uri(resource));
708      g_signal_connect (resource, "failed",
709                        G_CALLBACK (webkit_resource_load_failed_cb),
710                        data);
711      g_signal_connect (resource, "finished",
712                        G_CALLBACK (webkit_resource_load_finished_cb),
713                        data);
714 }
715 
716 /********************************************************************
717  * gnc_html_open_scm
718  * insert some scheme-generated HTML
719  ********************************************************************/
720 
721 static void
gnc_html_open_scm(GncHtmlWebkit * self,const gchar * location,const gchar * label,int newwin)722 gnc_html_open_scm( GncHtmlWebkit* self, const gchar * location,
723                    const gchar * label, int newwin )
724 {
725      PINFO("location='%s'", location ? location : "(null)");
726 }
727 
728 
729 /********************************************************************
730  * gnc_html_show_data
731  * display some HTML that the creator of the gnc-html got from
732  * somewhere.
733  ********************************************************************/
734 
735 static void
impl_webkit_show_data(GncHtml * self,const gchar * data,int datalen)736 impl_webkit_show_data( GncHtml* self, const gchar* data, int datalen )
737 {
738      GncHtmlWebkitPrivate* priv;
739 #define TEMPLATE_REPORT_FILE_NAME "gnc-report-XXXXXX.html"
740      int fd;
741      gchar* uri;
742      gchar *filename;
743 
744      g_return_if_fail( self != NULL );
745      g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
746 
747      ENTER( "datalen %d, data %20.20s", datalen, data );
748 
749      priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
750 
751      /* Export the HTML to a file and load the file URI.   On Linux, this seems to get around some
752         security problems (otherwise, it can complain that embedded images aren't permitted to be
753         viewed because they are local resources).  On Windows, this allows the embedded images to
754         be viewed (maybe for the same reason as on Linux, but I haven't found where it puts those
755         messages. */
756      filename = g_build_filename(g_get_tmp_dir(), TEMPLATE_REPORT_FILE_NAME, (gchar *)NULL);
757      fd = g_mkstemp( filename );
758      impl_webkit_export_to_file( self, filename );
759      close( fd );
760      uri = g_strdup_printf( "file://%s", filename );
761      g_free(filename);
762      DEBUG("Loading uri '%s'", uri);
763      webkit_web_view_load_uri( priv->web_view, uri );
764      g_free( uri );
765 
766      LEAVE("");
767 }
768 
769 /********************************************************************
770  * gnc_html_show_url
771  *
772  * open a URL.  This is called when the user clicks a link or
773  * for the creator of the gnc_html window to explicitly request
774  * a URL.
775  ********************************************************************/
776 
777 static void
impl_webkit_show_url(GncHtml * self,URLType type,const gchar * location,const gchar * label,gboolean new_window_hint)778 impl_webkit_show_url( GncHtml* self, URLType type,
779                       const gchar* location, const gchar* label,
780                       gboolean new_window_hint )
781 {
782      GncHTMLUrlCB url_handler;
783      gboolean new_window;
784      GncHtmlWebkitPrivate* priv;
785 
786      g_return_if_fail( self != NULL );
787      g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
788      g_return_if_fail( location != NULL );
789 
790      priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
791 
792      /* make sure it's OK to show this URL type in this window */
793      if ( new_window_hint == 0 )
794      {
795           if ( priv->base.urltype_cb )
796           {
797                new_window = !((priv->base.urltype_cb)( type ));
798           }
799           else
800           {
801                new_window = FALSE;
802           }
803      }
804      else
805      {
806           new_window = TRUE;
807      }
808 
809      if ( !new_window )
810      {
811           gnc_html_cancel( GNC_HTML(self) );
812      }
813 
814      if ( gnc_html_url_handlers )
815      {
816           url_handler = g_hash_table_lookup( gnc_html_url_handlers, type );
817      }
818      else
819      {
820           url_handler = NULL;
821      }
822 
823      if ( url_handler )
824      {
825           GNCURLResult result;
826           gboolean ok;
827 
828           result.load_to_stream = FALSE;
829           result.url_type = type;
830           result.location = NULL;
831           result.label = NULL;
832           result.base_type = URL_TYPE_FILE;
833           result.base_location = NULL;
834           result.error_message = NULL;
835           result.parent = GTK_WINDOW (priv->base.parent);
836 
837           ok = url_handler( location, label, new_window, &result );
838           if ( !ok )
839           {
840                if ( result.error_message )
841                {
842                    gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s", result.error_message );
843                }
844                else
845                {
846                     /* %s is a URL (some location somewhere). */
847                     gnc_error_dialog (GTK_WINDOW (priv->base.parent), _("There was an error accessing %s."), location );
848                }
849 
850                if ( priv->base.load_cb )
851                {
852                     priv->base.load_cb( GNC_HTML(self), result.url_type,
853                                         location, label, priv->base.load_cb_data );
854                }
855           }
856           else if ( result.load_to_stream )
857           {
858                gnc_html_history_node *hnode;
859                const char *new_location;
860                const char *new_label;
861 
862                new_location = result.location ? result.location : location;
863                new_label = result.label ? result.label : label;
864                hnode = gnc_html_history_node_new( result.url_type, new_location, new_label );
865 
866                gnc_html_history_append( priv->base.history, hnode );
867 
868                g_free( priv->base.base_location );
869                priv->base.base_type = result.base_type;
870                priv->base.base_location =
871                     g_strdup( extract_base_name( result.base_type, new_location ) );
872                DEBUG( "resetting base location to %s",
873                       priv->base.base_location ? priv->base.base_location : "(null)" );
874 
875                load_to_stream( GNC_HTML_WEBKIT(self), result.url_type,
876                                new_location, new_label );
877 
878                if ( priv->base.load_cb != NULL )
879                {
880                     priv->base.load_cb( GNC_HTML(self), result.url_type,
881                                         new_location, new_label, priv->base.load_cb_data );
882                }
883           }
884 
885           g_free( result.location );
886           g_free( result.label );
887           g_free( result.base_location );
888           g_free( result.error_message );
889 
890           return;
891      }
892 
893      if ( g_strcmp0( type, URL_TYPE_SCHEME ) == 0 )
894      {
895           gnc_html_open_scm( GNC_HTML_WEBKIT(self), location, label, new_window );
896 
897      }
898      else if ( g_strcmp0( type, URL_TYPE_JUMP ) == 0 )
899      {
900           /* Webkit jumps to the anchor on its own */
901      }
902      else if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 ||
903                g_strcmp0( type, URL_TYPE_HTTP ) == 0 ||
904                g_strcmp0( type, URL_TYPE_FILE ) == 0 )
905      {
906 
907           do
908           {
909                if ( g_strcmp0( type, URL_TYPE_SECURE ) == 0 )
910                {
911                     if ( !https_allowed() )
912                     {
913                         gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
914                                            _("Secure HTTP access is disabled. "
915                                              "You can enable it in the Network section of "
916                                              "the Preferences dialog.") );
917                          break;
918                     }
919                }
920 
921                if ( g_strcmp0( type, URL_TYPE_HTTP ) == 0 )
922                {
923                     if ( !http_allowed() )
924                     {
925                         gnc_error_dialog (GTK_WINDOW (priv->base.parent), "%s",
926                                            _("Network HTTP access is disabled. "
927                                              "You can enable it in the Network section of "
928                                              "the Preferences dialog.") );
929                          break;
930                     }
931                }
932 
933                priv->base.base_type = type;
934 
935                if ( priv->base.base_location != NULL ) g_free( priv->base.base_location );
936                priv->base.base_location = extract_base_name( type, location );
937 
938                /* FIXME : handle new_window = 1 */
939                gnc_html_history_append( priv->base.history,
940                                         gnc_html_history_node_new( type, location, label ) );
941                load_to_stream( GNC_HTML_WEBKIT(self), type, location, label );
942 
943           }
944           while ( FALSE );
945      }
946      else
947      {
948           PERR( "URLType %s not supported.", type );
949      }
950 
951      if ( priv->base.load_cb != NULL )
952      {
953           (priv->base.load_cb)( GNC_HTML(self), type, location, label, priv->base.load_cb_data );
954      }
955 }
956 
957 
958 /********************************************************************
959  * gnc_html_reload
960  * reload the current page
961  * if force_rebuild is TRUE, the report is recreated, if FALSE, report
962  * is reloaded by webkit
963  ********************************************************************/
964 
965 static void
impl_webkit_reload(GncHtml * self,gboolean force_rebuild)966 impl_webkit_reload( GncHtml* self, gboolean force_rebuild )
967 {
968      GncHtmlWebkitPrivate* priv;
969 
970      g_return_if_fail( self != NULL );
971      g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
972 
973      priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
974 
975      if ( force_rebuild )
976      {
977           gnc_html_history_node *n = gnc_html_history_get_current( priv->base.history );
978           if ( n != NULL )
979                gnc_html_show_url( self, n->type, n->location, n->label, 0 );
980      }
981      else
982           webkit_web_view_reload( priv->web_view );
983 }
984 
985 
986 /********************************************************************
987  * gnc_html_new
988  * create and set up a new webkit widget.
989  ********************************************************************/
990 
991 GncHtml*
gnc_html_webkit_new(void)992 gnc_html_webkit_new( void )
993 {
994      GncHtmlWebkit* self = g_object_new( GNC_TYPE_HTML_WEBKIT, NULL );
995      return GNC_HTML(self);
996 }
997 
998 /********************************************************************
999  * gnc_html_cancel
1000  * cancel any outstanding HTML fetch requests.
1001  ********************************************************************/
1002 
1003 static gboolean
webkit_cancel_helper(gpointer key,gpointer value,gpointer user_data)1004 webkit_cancel_helper(gpointer key, gpointer value, gpointer user_data)
1005 {
1006      g_free(key);
1007      g_list_free((GList *)value);
1008      return TRUE;
1009 }
1010 
1011 static void
impl_webkit_cancel(GncHtml * self)1012 impl_webkit_cancel( GncHtml* self )
1013 {
1014      GncHtmlWebkitPrivate* priv;
1015 
1016      g_return_if_fail( self != NULL );
1017      g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1018 
1019      priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1020 
1021      /* remove our own references to requests */
1022      //gnc_http_cancel_requests( priv->http );
1023 
1024      g_hash_table_foreach_remove( priv->base.request_info, webkit_cancel_helper, NULL );
1025 }
1026 
1027 static void
impl_webkit_copy_to_clipboard(GncHtml * self)1028 impl_webkit_copy_to_clipboard( GncHtml* self )
1029 {
1030      GncHtmlWebkitPrivate* priv;
1031 
1032      g_return_if_fail( self != NULL );
1033      g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1034 
1035      priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1036      webkit_web_view_execute_editing_command (priv->web_view,
1037                           WEBKIT_EDITING_COMMAND_COPY);
1038 }
1039 
1040 /**************************************************************
1041  * gnc_html_export_to_file
1042  *
1043  * @param self GncHtmlWebkit object
1044  * @param filepath Where to write the HTML
1045  * @return TRUE if successful, FALSE if unsuccessful
1046  **************************************************************/
1047 static gboolean
impl_webkit_export_to_file(GncHtml * self,const char * filepath)1048 impl_webkit_export_to_file( GncHtml* self, const char *filepath )
1049 {
1050      FILE *fh;
1051      GncHtmlWebkitPrivate* priv;
1052 
1053      g_return_val_if_fail( self != NULL, FALSE );
1054      g_return_val_if_fail( GNC_IS_HTML_WEBKIT(self), FALSE );
1055      g_return_val_if_fail( filepath != NULL, FALSE );
1056 
1057      priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1058      if ( priv->html_string == NULL )
1059      {
1060           return FALSE;
1061      }
1062      fh = g_fopen( filepath, "w" );
1063      if ( fh != NULL )
1064      {
1065           gint written;
1066           gint len = strlen( priv->html_string );
1067 
1068           written = fwrite( priv->html_string, 1, len, fh );
1069           fclose (fh);
1070 
1071           if ( written != len )
1072           {
1073                return FALSE;
1074           }
1075 
1076           return TRUE;
1077      }
1078      else
1079      {
1080           return FALSE;
1081      }
1082 }
1083 
1084 /* The webkit1 comment was
1085  * If printing on WIN32, in order to prevent the font from being tiny, (see bug
1086  * #591177), A GtkPrintOperation object needs to be created so that the unit can
1087  * be set, and then webkit_web_frame_print_full() needs to be called to use that
1088  * GtkPrintOperation.  On other platforms (specifically linux - not sure about
1089  * MacOSX), the version of webkit may not contain the function
1090  * webkit_web_frame_print_full(), so webkit_web_frame_print() is called instead
1091  * (the font size problem doesn't show up on linux).
1092  *
1093  * Webkit2 exposes only a very simple WebKitPrintOperation API. In order to
1094  * implement the above if it proves still to be necessary we'll have to use
1095  * GtkPrintOperation instead, passing it the results of
1096  * webkit_web_view_get_snapshot for each page.
1097  */
1098 static void
impl_webkit_print(GncHtml * self,const gchar * jobname)1099 impl_webkit_print (GncHtml* self,const gchar* jobname)
1100 {
1101      WebKitPrintOperation *op = NULL;
1102      GtkWindow *top = NULL;
1103      GncHtmlWebkitPrivate *priv;
1104      GtkPrintSettings *print_settings = NULL;
1105      WebKitPrintOperationResponse print_response;
1106      gchar *export_dirname = NULL;
1107      gchar *export_filename = NULL;
1108      gchar* basename = NULL;
1109      GKeyFile *state_file = gnc_state_get_current();
1110 
1111      g_return_if_fail (self != NULL);
1112      g_return_if_fail (GNC_IS_HTML_WEBKIT (self));
1113      priv = GNC_HTML_WEBKIT_GET_PRIVATE (self);
1114      op = webkit_print_operation_new (priv->web_view);
1115      basename = g_path_get_basename(jobname);
1116      print_settings = gtk_print_settings_new();
1117      webkit_print_operation_set_print_settings(op, print_settings);
1118      export_filename = g_strdup(jobname);
1119      g_free(basename);
1120      gtk_print_settings_set(print_settings,
1121                     GTK_PRINT_SETTINGS_OUTPUT_BASENAME,
1122                     export_filename);
1123      webkit_print_operation_set_print_settings(op, print_settings);
1124      // Open a print dialog
1125      top = GTK_WINDOW (gtk_widget_get_toplevel (GTK_WIDGET (priv->web_view)));
1126      print_response = webkit_print_operation_run_dialog (op, top);
1127      if (print_response == WEBKIT_PRINT_OPERATION_RESPONSE_PRINT)
1128      {
1129           // Get the newly updated print settings
1130           g_object_unref(print_settings);
1131           print_settings = g_object_ref(webkit_print_operation_get_print_settings(op));
1132      }
1133      g_free(export_dirname);
1134      g_free(export_filename);
1135      g_object_unref (op);
1136      g_object_unref (print_settings);
1137 }
1138 
1139 static void
impl_webkit_set_parent(GncHtml * self,GtkWindow * parent)1140 impl_webkit_set_parent( GncHtml* self, GtkWindow* parent )
1141 {
1142      GncHtmlWebkitPrivate* priv;
1143 
1144      g_return_if_fail( self != NULL );
1145      g_return_if_fail( GNC_IS_HTML_WEBKIT(self) );
1146 
1147      priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1148      priv->base.parent = GTK_WIDGET(parent);
1149 }
1150 
1151 static void
impl_webkit_default_zoom_changed(gpointer prefs,gchar * pref,gpointer user_data)1152 impl_webkit_default_zoom_changed(gpointer prefs, gchar *pref, gpointer user_data)
1153 {
1154      gdouble zoom = 1.0;
1155      GncHtmlWebkit* self = GNC_HTML_WEBKIT(user_data);
1156      GncHtmlWebkitPrivate* priv = GNC_HTML_WEBKIT_GET_PRIVATE(self);
1157 
1158      g_return_if_fail(user_data != NULL);
1159 
1160      zoom = gnc_prefs_get_float (GNC_PREFS_GROUP_GENERAL_REPORT, GNC_PREF_RPT_DFLT_ZOOM);
1161      webkit_web_view_set_zoom_level (priv->web_view, zoom);
1162 
1163 }
1164