From 2770c2c85c42020fd7c440b536804a4e70542c0d Mon Sep 17 00:00:00 2001
From: gb10 <gb10>
Date: Wed, 6 Oct 2010 10:55:13 +0000
Subject: [PATCH] Added better code for calling a web browser and used this for
 pfetch-www mode

---
 blixem_.h            |   3 +-
 blxFetch.c           | 238 +++++++++++++-------------
 seqtoolsWebBrowser.c | 387 +++++++++++++++++++++++++++++++++++++++++++
 utilities.h          |   6 +-
 4 files changed, 516 insertions(+), 118 deletions(-)
 create mode 100644 seqtoolsWebBrowser.c

diff --git a/blixem_.h b/blixem_.h
index 130c9ca2..2919f578 100644
--- a/blixem_.h
+++ b/blixem_.h
@@ -26,7 +26,7 @@
  * HISTORY:
  * Last edited: Aug 26 09:09 2009 (edgrif)
  * Created: Thu Nov 29 10:59:09 2001 (edgrif)
- * CVS info:   $Id: blixem_.h,v 1.53 2010-10-05 17:40:20 gb10 Exp $
+ * CVS info:   $Id: blixem_.h,v 1.54 2010-10-06 10:55:13 gb10 Exp $
  *-------------------------------------------------------------------
  */
 #ifndef DEF_BLIXEM_P_H
@@ -329,6 +329,7 @@ gboolean                           blxInitConfig(char *config_file, GError **err
 GKeyFile*                          blxGetConfig(void) ;
 gboolean                           blxConfigSetPFetchSocketPrefs(char *node, int port) ;
 gboolean                           blxConfigGetPFetchSocketPrefs(const char **node, int *port) ;
+gboolean                           blxConfigGetPFetchWWWPrefs();
 
 
 /* blxGff3Parser.c */
diff --git a/blxFetch.c b/blxFetch.c
index 78a784f7..794f1163 100644
--- a/blxFetch.c
+++ b/blxFetch.c
@@ -38,7 +38,7 @@
  * HISTORY:
  * Last edited: Aug 21 17:34 2009 (edgrif)
  * Created: Tue Jun 17 16:20:26 2008 (edgrif)
- * CVS info:   $Id: blxFetch.c,v 1.40 2010-10-05 15:51:21 gb10 Exp $
+ * CVS info:   $Id: blxFetch.c,v 1.41 2010-10-06 10:55:13 gb10 Exp $
  *-------------------------------------------------------------------
  */
 
@@ -361,73 +361,73 @@ static void externalCommand (char *command, GtkWidget *blxWindow)
 }
 
 
-#if !defined(ACEDB)
-/* Find an executable and return its complete pathname.
- */
-static int findCommand (char *command, const char **retp)
-{
-#if !defined(NO_POPEN)
-  static char retstr[1025] ;
-  char *path, file[1025], retval;
-  int found=0;
-  
-  /* Don't use csh - fails if the path is not set in .cshrc * /
-   if (access(csh, X_OK)) {
-   messout("Could not find %s", csh);
-   return 0;
-   }
-   if (!(pipe = (FILE *)popen(messprintf("%s -cf \"which %s\"", csh, command), "r"))) {
-   return 0;
-   }
-   
-   while (!feof(pipe))
-   fgets(retval, 1024, pipe);
-   retval[1024] = 0;
-   pclose(pipe);
-   
-   if (cp = strchr(retval, '\n')) *cp = 0;
-   if (retp) *retp = retval;
-   
-   / * Check if whatever "which" returned is an existing and executable file * /
-   if (!access(retval, F_OK) && !access(retval, X_OK))
-   return 1;
-   else
-   return 0;
-   */
-  
-  path = g_malloc(strlen(g_getenv("PATH"))+1);
-  /* Don't free 'path' since it changes later on - never mind, 
-   we're only calling it once */
-  
-  strcpy(path, g_getenv("PATH"));
-  path = strtok(path, ":");
-  while (path) {
-    strcpy(file, path);
-    strcat(file,"/");
-    strcat(file, command);
-    if (!access(file, F_OK) && !access(file, X_OK)) {
-      found = 1;
-      break;
-    }
-    
-    path = strtok(0, ":");
-  }
-  
-  if (found) {
-    strcpy(retstr, file);
-    retval = 1;
-  }
-  else {
-    strcpy(retstr, "Can't find executable in path");
-    retval = 0;
-  }
-  
-  if (retp) *retp = retstr;
-  return retval;
-  
-#endif
-}
-#endif
+//#if !defined(ACEDB)
+///* Find an executable and return its complete pathname.
+// */
+//static int findCommand (char *command, const char **retp)
+//{
+//#if !defined(NO_POPEN)
+//  static char retstr[1025] ;
+//  char *path, file[1025], retval;
+//  int found=0;
+//  
+//  /* Don't use csh - fails if the path is not set in .cshrc * /
+//   if (access(csh, X_OK)) {
+//   messout("Could not find %s", csh);
+//   return 0;
+//   }
+//   if (!(pipe = (FILE *)popen(messprintf("%s -cf \"which %s\"", csh, command), "r"))) {
+//   return 0;
+//   }
+//   
+//   while (!feof(pipe))
+//   fgets(retval, 1024, pipe);
+//   retval[1024] = 0;
+//   pclose(pipe);
+//   
+//   if (cp = strchr(retval, '\n')) *cp = 0;
+//   if (retp) *retp = retval;
+//   
+//   / * Check if whatever "which" returned is an existing and executable file * /
+//   if (!access(retval, F_OK) && !access(retval, X_OK))
+//   return 1;
+//   else
+//   return 0;
+//   */
+//  
+//  path = g_malloc(strlen(g_getenv("PATH"))+1);
+//  /* Don't free 'path' since it changes later on - never mind, 
+//   we're only calling it once */
+//  
+//  strcpy(path, g_getenv("PATH"));
+//  path = strtok(path, ":");
+//  while (path) {
+//    strcpy(file, path);
+//    strcat(file,"/");
+//    strcat(file, command);
+//    if (!access(file, F_OK) && !access(file, X_OK)) {
+//      found = 1;
+//      break;
+//    }
+//    
+//    path = strtok(0, ":");
+//  }
+//  
+//  if (found) {
+//    strcpy(retstr, file);
+//    retval = 1;
+//  }
+//  else {
+//    strcpy(retstr, "Can't find executable in path");
+//    retval = 0;
+//  }
+//  
+//  if (retp) *retp = retstr;
+//  return retval;
+//  
+//#endif
+//}
+//#endif
 
 
 /* Display the embl entry for a sequence via pfetch, efetch or whatever. */
@@ -438,6 +438,7 @@ void fetchAndDisplaySequence(char *seqName, GtkWidget *blxWindow)
 #endif
 {
   const char *fetchMode = blxWindowGetFetchMode(blxWindow);
+  GError *error = NULL;
   
   if (!strcmp(fetchMode, BLX_FETCH_PFETCH))
     {
@@ -457,39 +458,9 @@ void fetchAndDisplaySequence(char *seqName, GtkWidget *blxWindow)
     }
   else if (!strcmp(fetchMode, BLX_FETCH_WWW_EFETCH))
     {
-#ifdef ACEDB
-      char *command = blxprintf("%s%s", URL, seqName);
-      graphWebBrowser (command);
-      g_free(command);
-#else
-      {
-	const char *browser = g_getenv("BLIXEM_WWW_BROWSER");
-
-	if (!browser)
-	  {
-	    printf("Looking for WWW browsers ...\n");
-	    if (!findCommand("netscape", &browser) &&
-		!findCommand("Netscape", &browser) &&
-		!findCommand("Mosaic", &browser) &&
-		!findCommand("mosaic", &browser) &&
-		!findCommand("xmosaic", &browser) &&
-		!findCommand("firefox", &browser) &&
-		!findCommand("Safari", &browser))
-	      {
-		g_warning("Couldn't find any WWW browser.  Looked for "
-			  "netscape, Netscape, Mosaic, xmosaic, mosaic, firefox & Safari. "
-			  "System message: \"%s\"\n", browser);
-		return;
-	      }
-	  }
-	printf("Using WWW browser %s\n", browser);
-	fflush(stdout);
-        
-        char *command = blxprintf("%s %s%s&", browser, URL, seqName);
-	system(command);
-        g_free(command);
-      }
-#endif
+        char *link = blxprintf("%s%s", URL, seqName);
+	seqtoolsLaunchWebBrowser(link, &error);
+        g_free(link);
     }
 #ifdef ACEDB
   else if (!strcmp(fetchMode, BLX_FETCH_ACEDB))
@@ -502,15 +473,12 @@ void fetchAndDisplaySequence(char *seqName, GtkWidget *blxWindow)
     }
 #endif
   else
-    g_critical("Unknown fetchMode: %s", fetchMode);
-
-  if (!URL)
     {
-      char *tmpUrl = g_malloc(256);
-      strcpy(tmpUrl, "http://www.sanger.ac.uk/cgi-bin/seq-query?");
-      URL = tmpUrl;
+      g_critical("Unknown fetchMode: %s", fetchMode);
     }
 
+  reportAndClearIfError(&error, G_LOG_LEVEL_CRITICAL);
+
   return ;
 }
 
@@ -525,7 +493,7 @@ void blxFindInitialFetchMode(char *fetchMode)
     {
       strcpy(fetchMode, BLX_FETCH_PFETCH);
     }
-  else if ((URL = g_getenv("BLIXEM_FETCH_WWW")))
+  else if (g_getenv("BLIXEM_FETCH_WWW"))
     {
       strcpy(fetchMode, BLX_FETCH_WWW_EFETCH);
     }
@@ -1108,6 +1076,7 @@ gboolean blxConfigGetPFetchSocketPrefs(const char **node, int *port)
   return result ;
 }
 
+/* Set the preferences for pfetch mode from the config file */
 gboolean blxConfigSetPFetchSocketPrefs(char *node, int port)
 {
   gboolean result = TRUE ;				    /* Can't fail. */
@@ -1124,6 +1093,43 @@ gboolean blxConfigSetPFetchSocketPrefs(char *node, int port)
   return result ;
 }
 
+/* Set the preferences for pfetch-www mode from the config file */
+gboolean blxConfigGetPFetchWWWPrefs()
+{
+  gboolean result = TRUE ;				    /* Can't fail. */
+  
+  /* Try the environment var first */
+  URL = g_getenv("BLIXEM_FETCH_WWW");
+
+  if (!URL)
+    {
+      GKeyFile *key_file = blxGetConfig() ;
+
+      if (key_file && g_key_file_has_group(key_file, PFETCH_PROXY_GROUP))
+        {
+          /* Note that this call will create the URL if it doesn't exist but will
+           * overwrite any existing value. */
+          GError *error = NULL;
+
+          char *tmpUrl = g_malloc(256);
+          tmpUrl = g_key_file_get_string(key_file, PFETCH_PROXY_GROUP, PFETCH_PROXY_LOCATION, &error) ;
+          reportAndClearIfError(&error, G_LOG_LEVEL_CRITICAL);
+          
+          URL = blxprintf("%s?request=", tmpUrl);
+          g_free(tmpUrl);
+        }
+          
+      if (!URL || *URL == '\0')
+        {
+          char *tmpUrl = g_malloc(256);
+          strcpy(tmpUrl, "http://www.sanger.ac.uk/cgi-bin/seq-query?");
+          URL = tmpUrl;
+        }
+    }
+
+
+  return result ;
+}
 
 
 
@@ -1838,15 +1844,15 @@ static gboolean setupPfetchMode(PfetchParams *pfetch, const char *fetchMode, con
 /* Set up the fetch mode. Sets the fetch-mode, and also net_id and port if relevant */
 void setupFetchMode(PfetchParams *pfetch, char **fetchMode, const char **net_id, int *port)
 {
-  /* First, set the fetch mode */
-  if (pfetch)
+  /* Set up the pfetch and www-pfetch config from the file / env vars etc. We need
+   * to set them up even if we're not using that mode initially, because the user can 
+   * change the mode */
+  blxConfigGetPFetchWWWPrefs();
+
+  /* Set the fetch mode */
+  if (pfetch && blxConfigSetPFetchSocketPrefs(pfetch->net_id, pfetch->port))
     {
-      /* If pfetch struct then this sets fetch mode to pfetch. */
-      
-      if (blxConfigSetPFetchSocketPrefs(pfetch->net_id, pfetch->port))
-	{
-	  *fetchMode = BLX_FETCH_PFETCH;
-	}
+      *fetchMode = BLX_FETCH_PFETCH;
     }
   else
     {
diff --git a/seqtoolsWebBrowser.c b/seqtoolsWebBrowser.c
new file mode 100644
index 00000000..3f8d1678
--- /dev/null
+++ b/seqtoolsWebBrowser.c
@@ -0,0 +1,387 @@
+/*  File: seqtoolsWebBrowser.c
+ *  Author: Ed Griffiths (edgrif@sanger.ac.uk)
+ *  Copyright (c) 2006-2010: Genome Research Ltd.
+ *-------------------------------------------------------------------
+ * SeqTools 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, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
+ * or see the on-line version at http://www.gnu.org/copyleft/gpl.txt
+ *-------------------------------------------------------------------
+ * This file is part of the SeqTools sequenceing tools package.
+ * Code was taken from the ZMap genome database package,
+ * originated by
+ *      Ed Griffiths (Sanger Institute, UK) edgrif@sanger.ac.uk,
+ *        Roy Storey (Sanger Institute, UK) rds@sanger.ac.uk,
+ *     Malcolm Hinsley (Sanger Institute, UK) mh17@sanger.ac.uk
+ * adapted for SeqTools by
+ *      Gemma Barson (Sanger Institute, UK) gb10@sanger.ac.uk
+ *
+ * Description: Functions to display a url in a web browser.
+ *
+ * Exported functions: See utilities.h
+ *-------------------------------------------------------------------
+ */
+
+#include <SeqTools/utilities.h>
+#include <sys/utsname.h>
+#include <glib.h>
+#include <stdlib.h>
+#include <string.h>
+
+/* Describes various browsers, crude at the moment, we will probably need more options later. */
+typedef struct
+{
+  char *system ;					    /* system name as in "uname -s" */
+  char *executable ;					    /* executable name or full path. */
+  char *open_command ;					    /* alternative command to start browser. */
+} BrowserConfigStruct, *BrowserConfig ;
+
+
+static char *findBrowser(BrowserConfig browsers, BrowserConfig *browser_out, GError **error) ;
+static void makeBrowserCmd(GString *cmd, BrowserConfig best_browser, char *url) ;
+static char *translateURLChars(char *orig_link) ;
+gboolean seqtools_g_string_replace(GString *string, char *target, char *source);
+
+
+/* Records information for running a specific browser. The intent here is to add enough
+ * information to allow us to use stuff like Netscapes "open" subcommand which opens the
+ * url in an already running netscape if there is one, otherwise in a new netscape.
+ * 
+ * (see w2/graphgdkremote.c in acedb for some useful stuff.)
+ * 
+ * (See manpage for more options for "open" on the Mac, e.g. we could specify to always
+ * use Safari via the -a flag...which would also deal with badly specified urls...)
+ * 
+ * In the open_command the %U is substituted with the URL.
+ * 
+ * Note that if open_command is NULL then the executable name is simply combined with
+ * the URL in the expected way to form the command:    "executable  URL"
+ * 
+ * 
+ * Here's one from gnome...this will start in a new window or start a new browser as required.
+ * /usr/bin/gnome-moz-remote --newwin www.acedb.org
+ * 
+ * 
+ *  */
+#define BROWSER_PATTERN "%U"
+
+/* List of browsers for different systems, you can have more than one browser for a system. */
+static BrowserConfigStruct browsers_G[] =
+  {
+    {"Linux",  "iceweasel",  "iceweasel -new-window \""BROWSER_PATTERN"\""},
+    {"Linux",  "firefox",  "firefox -browser \""BROWSER_PATTERN"\""},
+    {"Linux",  "mozilla",  "mozilla -remote 'openurl(\""BROWSER_PATTERN"\",new-window)' || mozilla \""BROWSER_PATTERN"\""},
+    {"OSF",    "netscape", NULL},
+    {"Darwin", "/Applications/Safari.app/Contents/MacOS/Safari", "open \""BROWSER_PATTERN"\""},
+    {NULL, NULL}					    /* Terminator record. */
+  } ;
+
+
+/* Error handling stuff. */
+static char *domain_G = "SEQTOOLS_WEB" ;
+enum {BROWSER_NOT_FOUND, BROWSER_COMMAND_FAILED, BROWSER_UNAME_FAILED, BROWSER_NOT_REGISTERED} ;
+static GQuark err_domain_G = 0 ;
+
+
+
+
+/*! @addtogroup seqtoolsutils
+ * @{ || 
+ *  */
+
+
+/*!
+ * Launches a web browser to display the specified link. The browser is chosen
+ * from an internal list of browsers for different machines. As so much can go
+ * wrong this function returns FALSE and a GError struct when an error occurs.
+ * You should call this function like this:
+ * 
+ *     GError *error = NULL ;
+ * 
+ *     if (!(seqtoolsLaunchWebBrowser("www.acedb.org", &error)))
+ *       { || 
+ *         printf("Error: %s\n", error->message) ;
+ * 
+ *         g_error_free(error) ;
+ *       }
+ *
+ * @param    link              url to be shown in browser.
+ * @param    error             pointer to NULL GError pointer for return of errors.
+ * @return   gboolean          TRUE if launch of browser successful.
+ */
+gboolean seqtoolsLaunchWebBrowser(char *link, GError **error)
+{
+  gboolean result = FALSE ;
+  BrowserConfig best_browser = NULL ;
+  char *browser = NULL ;
+
+  g_assert(link && *link && error && !(*error)) ; 
+
+  if (!err_domain_G)
+    err_domain_G = g_quark_from_string(domain_G) ;
+
+
+  /* Check we have a registered browser for this system. */
+  browser = findBrowser(browsers_G, &best_browser, error) ;
+
+
+
+  /* Run the browser in a separate process. */
+  if (browser)
+    {
+      char *url ;
+      GString *sys_cmd ;
+      int sys_rc ;
+
+      /* Translate troublesome chars to their url escape sequences, see translateURLChars() for explanation. */
+      url = translateURLChars(link) ;  
+
+      sys_cmd = g_string_sized_new(1024) ;		    /* Should be long enough for most urls. */
+
+      if (best_browser->open_command)
+	{
+	  makeBrowserCmd(sys_cmd, best_browser, url) ;
+	}
+      else 
+	{
+	  g_string_printf(sys_cmd, "%s \"%s\"", browser, url) ;
+	}
+
+      /* Make sure browser is run in background by the shell so we do not wait.
+       * NOTE that because we do not wait for the command to be executed,
+       * we cannot tell if the command actually worked, only that the shell
+       * got exec'd */
+      g_string_append(sys_cmd, " &") ;    
+
+      /* We could do much more to interpret what exactly failed here... */
+      if ((sys_rc = system(sys_cmd->str)) == EXIT_SUCCESS)
+	{
+	  result = TRUE ;
+	}
+      else   
+	{
+	  *error = g_error_new(err_domain_G, BROWSER_COMMAND_FAILED,
+			       "Failed to run command \"%s\".", sys_cmd->str) ;
+	}
+
+      g_string_free(sys_cmd, TRUE) ;
+      g_free(url) ;
+    }
+
+
+  return result ;
+}
+
+
+/*! @} end of seqtoolsutils docs. */
+
+
+
+
+
+
+/* 
+ *                Internal functions.
+ */
+
+
+
+
+/* Gets the system name and then finds browsers for that system from our || 
+ * our internal list, if it finds the browser is in the users path then
+ * returns the path otherwise returns NULL and sets error to give details
+ * of what went wrong. */
+static char *findBrowser(BrowserConfig browsers_in, BrowserConfig *browser_out, GError **error)
+{
+  char *browser = NULL ;
+  struct utsname unamebuf ;
+  gboolean browser_in_list = FALSE ;
+
+  if (uname(&unamebuf) == -1)
+    {
+      *error = g_error_new_literal(err_domain_G, BROWSER_UNAME_FAILED,
+				   "uname() call to find system name failed") ;
+    }
+  else 
+    {
+      BrowserConfig curr_browser = browsers_in ;
+      
+      while (curr_browser->system != NULL)
+	{
+	  if (g_ascii_strcasecmp(curr_browser->system, unamebuf.sysname) == 0)
+	    {
+	      browser_in_list = TRUE ; 
+
+	      /* Look for the browser in the users path. */
+	      if ((browser = g_find_program_in_path(curr_browser->executable)))
+		{
+		  *browser_out = curr_browser ;
+
+		  break ;
+		}
+	    }
+
+	  curr_browser++ ;
+	}
+    }
+
+  if (!browser)
+    {
+      if (browser_in_list)
+	{
+	  *error = g_error_new(err_domain_G, BROWSER_NOT_FOUND,
+			       "Browser(s) registered with SeqTools for this system (%s)"
+			       " but none found in $PATH or they were not executable.", unamebuf.sysname) ;
+	}
+      else
+	{
+	  *error = g_error_new(err_domain_G, BROWSER_NOT_REGISTERED,
+			       "No browser registered for system %s", unamebuf.sysname) ;
+	}
+    }
+
+  return browser ;
+}
+
+
+static void makeBrowserCmd(GString *cmd, BrowserConfig best_browser, char *url)
+{
+  gboolean found ;
+
+  cmd = g_string_append(cmd, best_browser->open_command) ;
+
+  found = seqtools_g_string_replace(cmd, BROWSER_PATTERN, url) ;
+
+  g_assert(found) ;					    /* Must find at least one pattern. */
+
+  return ;
+}
+
+
+/* URL standards provide escape sequences for all chars which gets round problems
+ * with them being wrongly interpreted. This function translates chars in the url
+ * that give us problems for various reasons:
+ *
+ * If we use the netscape or Mozilla "OpenURL" remote commands to display links, then sadly the
+ * syntax for this command is: "OpenURL(URL[,new-window])" and stupid
+ * netscape/mozilla will think that any "," in the url (i.e. lots of cgi links have
+ * "," to separate args !) is the "," for its OpenURL command and will then
+ * usually report a syntax error in the OpenURL command.                   
+ *                                                                         
+ * To get round this we translate any "," into "%2C" which is the standard 
+ * code for a "," in urls (thanks to Roger Pettet for this)....YUCH.
+ * 
+ * Some urls have single quotes in them, if you pass the whole string through
+ * to the shell these get wrongly interpreted by the shell in its normal
+ * string fashion so we translate them all to "%27".
+ * 
+ * mh17: for security we need to patch out other shell special characters such as '|', ';', '`'. >,< should be harmless
+ * Anything that allows a user to run another command is a no-no
+ * This gets inefficient (perl and php might do this better)
+ * we can quote the url, but that opens the door to interpretation, better to code metachars as hex.
+ "
+ * The returned string should be g_free'd when no longer needed.
+ *
+ *    mh17: second thoughts: don't use, these rely on quoting: thrid thoughts: no amount of escaped quoting seems to work
+ */
+static char *translateURLChars(char *orig_link)
+{
+  char *url = NULL ;
+  
+#if TRANSLATE
+
+  GString *link ;
+  char *target, *source ;
+
+  link = g_string_new(orig_link) ;
+
+  target = " " ;
+  source = "%20" ;
+  seqtools_g_string_replace(link, target, source) ;
+
+  target = ";" ;
+  source = "%3B" ;
+  seqtools_g_string_replace(link, target, source) ;
+
+  target = "," ;
+  source = "%2C" ;
+  seqtools_g_string_replace(link, target, source) ;
+
+  target = "'" ;
+  source = "%27" ;
+  seqtools_g_string_replace(link, target, source) ;
+
+  target = "&" ;
+  source = "%26" ;
+  seqtools_g_string_replace(link, target, source) ;
+
+  target = "|" ;
+  source = "%7C" ;
+  seqtools_g_string_replace(link, target, source) ;
+
+  target = "`" ;
+  source = "%60" ;
+  seqtools_g_string_replace(link, target, source) ;
+
+  url = g_string_free(link, FALSE) ;
+#else
+  url = g_strdup(orig_link);
+#endif
+  return url ;
+}
+
+
+
+
+/*!
+ * Substitute one string for another in a GString.
+ *
+ * You should note that this routine simply uses the existing GString erase/insert
+ * functions and so may not be incredibly efficient. If the target string was not
+ * found the GString remains unaltered and FALSE is returned.
+ *
+ * @param string                 A valid GString.
+ * @param target                 The string to be replaced.
+ * @param source                 The string to be inserted.
+ * @return                       TRUE if a string was replaced, FALSE otherwise.
+ *  */
+gboolean seqtools_g_string_replace(GString *string, char *target, char *source)
+{
+  gboolean result = FALSE ;
+  int source_len ;
+  int target_len ;
+  int target_pos ;
+  char *template_ptr ;
+  
+  
+  target_len = strlen(target) ;
+  source_len = strlen(source) ;
+  
+  template_ptr = string->str ;
+  while ((template_ptr = strstr(template_ptr, target)))
+    {
+      result = TRUE ;
+      
+      target_pos = template_ptr - string->str ;
+      
+      string = g_string_erase(string, target_pos, target_len) ;
+      
+      string = g_string_insert(string, target_pos, source) ;
+      
+      template_ptr = string->str + target_pos + source_len ; /* Shouldn't go off the end. */
+    }
+  
+  return result ;
+}
+
+
diff --git a/utilities.h b/utilities.h
index 41d28f1c..46605ab8 100644
--- a/utilities.h
+++ b/utilities.h
@@ -376,7 +376,6 @@ void                  drawHighlightBox(GdkDrawable *drawable, const GdkRectangle
 char*                 blxprintf(char *formatStr, ...);
 void                  setStatusBarShadowStyle(GtkWidget *statusBar, const char *shadowStyle);
 
-
 void    gtk_text_buffer_insert_markup             (GtkTextBuffer *buffer,
                                                    GtkTextIter   *iter,
                                                    const gchar   *markup);
@@ -393,4 +392,9 @@ void    gtk_text_buffer_set_markup_with_tag       (GtkTextBuffer *buffer,
 void    gtk_text_buffer_set_markup                (GtkTextBuffer *buffer,
                                                    const gchar   *markup);
 
+
+/* seqtoolsWebBrowser.c */
+gboolean              seqtoolsLaunchWebBrowser(char *link, GError **error);
+
+
 #endif /* _utilities_h_included_ */
-- 
GitLab