diff --git a/configure.in b/configure.in index fb3d126bd6..cd42d52150 100644 --- a/configure.in +++ b/configure.in @@ -1103,7 +1103,18 @@ esac AC_SUBST(GTKHTML_CFLAGS) AC_SUBST(GTKHTML_LIBS) - + + ###----------------------------------------------------------------------- + ## Find a suitable password store + + if x$host_os = xdarwin + then + AC_DEFINE(HAVE_OSX_KEYCHAIN,1,[System has an OS X Key chain]) + else + PKG_CHECK_MODULES(GNOME_KEYRING, gnome-keyring-1 >= "0.6", + [AC_DEFINE(HAVE_GNOME_KEYRING,1,[System has gnome-keyring 0.6 or better])], + [AC_DEFINE(HAVE_NO_KEYRING,1,[System has no suitable keyring service])]) + fi ### ---------------------------------------------------------------------- AC_ARG_ENABLE( efence, diff --git a/po/POTFILES.in b/po/POTFILES.in index 8b9c92944e..4c35a217da 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -153,6 +153,7 @@ src/core-utils/gnc-gkeyfile-utils.c src/core-utils/gnc-glib-utils.c src/core-utils/gnc-main.c src/core-utils/gnc-path.c +src/core-utils/gnc-uri-utils.c src/doc/doxygen_main_page.c src/engine/Account.c src/engine/cap-gains.c @@ -163,7 +164,6 @@ src/engine/gnc-associate-account.c src/engine/gnc-budget.c src/engine/gnc-commodity.c src/engine/gnc-engine.c -src/engine/gnc-filepath-utils.c src/engine/gnc-hooks.c src/engine/gnc-lot.c src/engine/gncmod-engine.c @@ -315,6 +315,7 @@ src/gnome-utils/gnc-gobject-utils.c src/gnome-utils/gnc-gtk-utils.c src/gnome-utils/gnc-gui-query.c src/gnome-utils/gnc-icons.c +src/gnome-utils/gnc-keyring.c src/gnome-utils/gnc-main-window.c src/gnome-utils/gnc-menu-extensions.c src/gnome-utils/gncmod-gnome-utils.c diff --git a/src/backend/dbi/gnc-backend-dbi.c b/src/backend/dbi/gnc-backend-dbi.c index 4f832488ad..c6d128e828 100644 --- a/src/backend/dbi/gnc-backend-dbi.c +++ b/src/backend/dbi/gnc-backend-dbi.c @@ -50,6 +50,7 @@ #include "Recurrence.h" #include "gnc-gconf-utils.h" +#include "gnc-uri-utils.h" #include "gnc-backend-dbi.h" @@ -220,6 +221,7 @@ gnc_dbi_sqlite3_session_begin( QofBackend *qbe, QofSession *session, gint result; gchar* dirname; gchar* basename; + gchar *filepath = NULL; g_return_if_fail( qbe != NULL ); g_return_if_fail( session != NULL ); @@ -228,17 +230,10 @@ gnc_dbi_sqlite3_session_begin( QofBackend *qbe, QofSession *session, ENTER (" "); /* Remove uri type if present */ - if ( g_str_has_prefix( book_id, FILE_URI_PREFIX ) ) - { - book_id += strlen( FILE_URI_PREFIX ); - } - if ( g_str_has_prefix( book_id, SQLITE3_URI_PREFIX ) ) - { - book_id += strlen( SQLITE3_URI_PREFIX ); - } + filepath = gnc_uri_get_path ( book_id ); if ( !create_if_nonexistent - && !g_file_test( book_id, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS ) ) + && !g_file_test( filepath, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS ) ) { qof_backend_set_error( qbe, ERR_FILEIO_FILE_NOT_FOUND ); LEAVE(" "); @@ -258,8 +253,9 @@ gnc_dbi_sqlite3_session_begin( QofBackend *qbe, QofSession *session, return; } - dirname = g_path_get_dirname( book_id ); - basename = g_path_get_basename( book_id ); + dirname = g_path_get_dirname( filepath ); + basename = g_path_get_basename( filepath ); + g_free ( filepath ); dbi_conn_error_handler( be->conn, sqlite3_error_fn, be ); result = dbi_conn_set_option( be->conn, "host", "localhost" ); if ( result < 0 ) @@ -406,13 +402,12 @@ gnc_dbi_mysql_session_begin( QofBackend* qbe, QofSession *session, gboolean create_if_nonexistent ) { GncDbiBackend *be = (GncDbiBackend*)qbe; - gchar* dsn = NULL; - gchar* host; - gchar* port = NULL; - gchar* dbname; - gchar* username; - gchar* password; - gint portnum; + gchar* protocol = NULL; + gchar* host = NULL; + gchar* dbname = NULL; + gchar* username = NULL; + gchar* password = NULL; + gint portnum = 0; gint result; gboolean success = FALSE; @@ -422,32 +417,11 @@ gnc_dbi_mysql_session_begin( QofBackend* qbe, QofSession *session, ENTER (" "); - /* Split the book-id (format host:dbname:username:password or - host:port:dbname:username:password) */ - dsn = g_strdup( book_id ); - for ( host = dsn; *host != '/'; host++ ) {} - host += 2; - for ( dbname = host; *dbname != ':'; dbname++ ) {} - *dbname++ = '\0'; - if ( *dbname >= '0' && *dbname <= '9' ) - { - port = dbname; - for ( ; *dbname != ':'; dbname++ ) {} - *dbname++ = '\0'; - } - for ( username = dbname; *username != ':'; username++ ) {} - *username++ = '\0'; - for ( password = username; *password != ':'; password++ ) {} - *password++ = '\0'; - - if ( port != NULL && *port != '\0' ) - { - portnum = atoi( port ); - } - else - { - portnum = 0; - } + /* Split the book-id + * Format is protocol://username:password@hostname:port/dbname + where username, password and port are optional) */ + gnc_uri_get_components ( book_id, &protocol, &host, portnum, + &username, &password, &dbname ); // Try to connect to the db. If it doesn't exist and the create_if_nonexistent // flag is TRUE, we'll need to connect to the 'mysql' db and execute the @@ -549,10 +523,11 @@ gnc_dbi_mysql_session_begin( QofBackend* qbe, QofSession *session, } be->sql_be.timespec_format = MYSQL_TIMESPEC_STR_FORMAT; exit: - if ( dsn != NULL ) - { - g_free( dsn ); - } + g_free( protocol ); + g_free( host ); + g_free( username ); + g_free( password ); + g_free( dbname ); LEAVE (" "); } @@ -586,15 +561,14 @@ gnc_dbi_postgres_session_begin( QofBackend *qbe, QofSession *session, gboolean create_if_nonexistent ) { GncDbiBackend *be = (GncDbiBackend*)qbe; - gint result; - gchar* dsn; - gchar* host; - gchar* port = NULL; - gchar* dbname; - gchar* username; - gchar* password; + gint result = 0; + gchar* protocol = NULL; + gchar* host = NULL; + gchar* dbname = NULL; + gchar* username = NULL; + gchar* password = NULL; gboolean success = FALSE; - gint portnum; + gint portnum = 0; g_return_if_fail( qbe != NULL ); g_return_if_fail( session != NULL ); @@ -602,32 +576,13 @@ gnc_dbi_postgres_session_begin( QofBackend *qbe, QofSession *session, ENTER (" "); - /* Split the book-id (format host:dbname:username:password or - host:port:dbname:username:password) */ - dsn = g_strdup( book_id ); - for ( host = dsn; *host != '/'; host++ ) {} - host += 2; - for ( dbname = host; *dbname != ':'; dbname++ ) {} - *dbname++ = '\0'; - if ( *dbname >= '0' && *dbname <= '9' ) - { - port = dbname; - for ( ; *dbname != ':'; dbname++ ) {} - *dbname++ = '\0'; - } - for ( username = dbname; *username != ':'; username++ ) {} - *username++ = '\0'; - for ( password = username; *password != ':'; password++ ) {} - *password++ = '\0'; - - if ( port != NULL && *port != '\0' ) - { - portnum = atoi( port ); - } - else - { + /* Split the book-id + * Format is protocol://username:password@hostname:port/dbname + where username, password and port are optional) */ + gnc_uri_get_components ( book_id, &protocol, &host, portnum, + &username, &password, &dbname ); + if ( portnum == 0 ) portnum = PGSQL_DEFAULT_PORT; - } // Try to connect to the db. If it doesn't exist and the create_if_nonexistent // flag is TRUE, we'll need to connect to the 'postgres' db and execute the @@ -729,10 +684,11 @@ gnc_dbi_postgres_session_begin( QofBackend *qbe, QofSession *session, } be->sql_be.timespec_format = PGSQL_TIMESPEC_STR_FORMAT; exit: - if ( dsn != NULL ) - { - g_free( dsn ); - } + g_free( protocol ); + g_free( host ); + g_free( username ); + g_free( password ); + g_free( dbname ); LEAVE (" "); } @@ -1004,17 +960,20 @@ gnc_dbi_provider_free( /*@ only @*/ QofBackendProvider *prov ) * */ static gboolean -gnc_dbi_check_sqlite3_file( const gchar *path ) +gnc_dbi_check_sqlite3_file( const gchar *uri ) { FILE* f; gchar buf[50]; size_t chars_read; gint status; + gchar *filename; // BAD if the path is null - g_return_val_if_fail( path != NULL, FALSE ); + g_return_val_if_fail( uri != NULL, FALSE ); - f = g_fopen( path, "r" ); + filename = gnc_uri_get_path ( uri ); + f = g_fopen( filename, "r" ); + g_free ( filename ); // OK if the file doesn't exist - new file if ( f == NULL ) diff --git a/src/backend/xml/gnc-backend-xml.c b/src/backend/xml/gnc-backend-xml.c index b9d2c43060..047ce39eaf 100644 --- a/src/backend/xml/gnc-backend-xml.c +++ b/src/backend/xml/gnc-backend-xml.c @@ -64,7 +64,7 @@ typedef int ssize_t; #include "TransLog.h" #include "gnc-engine.h" -#include "gnc-filepath-utils.h" +#include "gnc-uri-utils.h" #include "io-gncxml.h" #include "io-gncxml-v2.h" @@ -227,11 +227,8 @@ xml_session_begin(QofBackend *be_start, QofSession *session, ENTER (" "); /* Make sure the directory is there */ - if (g_str_has_prefix(book_id, XML_URI_PREFIX)) - book_id += strlen(XML_URI_PREFIX); - if (g_str_has_prefix(book_id, FILE_URI_PREFIX)) - book_id += strlen(FILE_URI_PREFIX); - be->fullpath = xaccResolveFilePath(book_id); + be->fullpath = gnc_uri_get_path (book_id); + if (NULL == be->fullpath) { qof_backend_set_error (be_start, ERR_FILEIO_FILE_NOT_FOUND); @@ -504,54 +501,61 @@ gnc_xml_be_determine_file_type(const char *path) } static gboolean -gnc_determine_file_type (const char *path) +gnc_determine_file_type (const char *uri) { struct stat sbuf; int rc; FILE *t; + gchar *filename; + gboolean result; - if (!path) + if (!uri) { return FALSE; } - // Since this can be called with "xml:" as a prefix, remove it if it exists - if ( g_str_has_prefix( path, "xml:" ) ) + filename = gnc_uri_get_path ( uri ); + if (0 == safe_strcmp(filename, QOF_STDOUT)) { - path += 4; + result = FALSE; + goto det_exit; } - - if (0 == safe_strcmp(path, QOF_STDOUT)) - { - return FALSE; - } - t = g_fopen(path, "r"); + t = g_fopen( filename, "r" ); if (!t) { PINFO (" new file"); - return TRUE; + result = TRUE; + goto det_exit; } fclose(t); - rc = g_stat(path, &sbuf); + rc = g_stat(filename, &sbuf); if (rc < 0) { - return FALSE; + result = FALSE; + goto det_exit; } if (sbuf.st_size == 0) { PINFO (" empty file"); - return TRUE; + result = TRUE; + goto det_exit; } - if (gnc_is_xml_data_file_v2(path, NULL)) + if (gnc_is_xml_data_file_v2(filename, NULL)) { - return TRUE; + result = TRUE; + goto det_exit; } - else if (gnc_is_xml_data_file(path)) + else if (gnc_is_xml_data_file(filename)) { - return TRUE; + result = TRUE; + goto det_exit; } - PINFO (" %s is not a gnc XML file", path); - return FALSE; + PINFO (" %s is not a gnc XML file", filename); + result = FALSE; + +det_exit: + g_free ( filename ); + return result; } static gboolean diff --git a/src/core-utils/Makefile.am b/src/core-utils/Makefile.am index 724af748de..ab53af9cb4 100644 --- a/src/core-utils/Makefile.am +++ b/src/core-utils/Makefile.am @@ -11,6 +11,7 @@ libgnc_core_utils_la_SOURCES = \ gnc-gkeyfile-utils.c \ gnc-glib-utils.c \ gnc-path.c \ + gnc-uri-utils.c \ swig-core-utils.c libgnc_core_utils_la_LIBADD = \ @@ -28,7 +29,8 @@ noinst_HEADERS = \ gnc-gdate-utils.h \ gnc-gkeyfile-utils.h \ gnc-glib-utils.h \ - gnc-path.h + gnc-path.h \ + gnc-uri-utils.h if BUILDING_FROM_SVN swig-core-utils.c: core-utils.i ${top_srcdir}/src/base-typemaps.i diff --git a/src/core-utils/gnc-filepath-utils.c b/src/core-utils/gnc-filepath-utils.c index efe6d4b15c..4a16ede695 100644 --- a/src/core-utils/gnc-filepath-utils.c +++ b/src/core-utils/gnc-filepath-utils.c @@ -24,10 +24,6 @@ * @brief file path resolution utilities * @author Copyright (c) 1998-2004 Linas Vepstas * @author Copyright (c) 2000 Dave Peticolas - * - * XXX this file does not belong in the gnucash engine; it is here - * for the moment only because both the file backend and the app-file - * GUI code make use of it. */ #include "config.h" @@ -47,7 +43,6 @@ #endif #include -#include "qof.h" #include "gnc-path.h" #include "gnc-filepath-utils.h" @@ -56,6 +51,7 @@ #define PATH_MAX MAXPATHLEN #endif + /** * Scrubs a filename by changing "strange" chars (e.g. those that are not * valid in a win32 file name) to "_". @@ -86,37 +82,44 @@ static gchar * check_path_return_if_valid(gchar *path) { if (g_file_test(path, G_FILE_TEST_IS_REGULAR)) + { return path; + } g_free (path); return NULL; } -/** @fn char * xaccResolveFilePath (const char * filefrag) +/** @fn char * gnc_resolve_file_path (const char * filefrag) * * @brief Create an absolute path when given a relative path; * otherwise return the argument. * - * If passed a string which g_path_is_absolute declares an absolute - * path, return the argument. If the string begins with file:, - * file://, xml:, or xml://, remove that and return the rest. + * @warning filefrag should be a simple path fragment. It shouldn't + * contain xml:// or http:// or :// other protocol specifiers. * - * Otherwise, assume that filefrag is a well-formed relative path and - * try to find a file with its path relative to the current working - * directory, the installed system-wide data directory (e.g., - * /usr/local/share/gnucash), the installed system configuration - * directory (e.g., /usr/local/etc/gnucash), or in the user's - * configuration directory (e.g., $HOME/.gnucash/data) in that - * order. If a matching file is found, return the absolute path to - * it. If one isn't found, return a absolute path relative to the - * user's configuration directory and note in the trace file that it - * needs to be created. + * If passed a string which g_path_is_absolute declares an absolute + * path, return the argument. * - * @param filefrag + * Otherwise, assume that filefrag is a well-formed relative path and + * try to find a file with its path relative to + * \li the current working directory, + * \li the installed system-wide data directory (e.g., /usr/local/share/gnucash), + * \li the installed system configuration directory (e.g., /usr/local/etc/gnucash), + * \li or in the user's configuration directory (e.g., $HOME/.gnucash/data) * - * @return An absolute file path. + * The paths are searched for in that order. If a matching file is + * found, return the absolute path to it. + + * If one isn't found, return a absolute path relative to the + * user's configuration directory and note in the trace file that it + * needs to be created. + * + * @param filefrag The file path to resolve + * + * @return An absolute file path. */ -char * -xaccResolveFilePath (const char * filefrag) +gchar * +gnc_resolve_file_path (const gchar * filefrag) { int namelen; gchar *fullpath = NULL, *tmp_path = NULL; @@ -135,22 +138,6 @@ xaccResolveFilePath (const char * filefrag) if (g_path_is_absolute(filefrag)) return g_strdup (filefrag); - if (!g_ascii_strncasecmp(filefrag, "file:", 5)) - { - if (!g_ascii_strncasecmp(filefrag, "file://", 7)) - return g_strdup(filefrag + 7); - else - return g_strdup(filefrag + 5); - } - if ( g_ascii_strncasecmp( filefrag, "xml:", 4 ) == 0 ) - { - if ( g_ascii_strncasecmp( filefrag, "xml://", 6 ) == 0 ) - return g_strdup( filefrag + 6); - else - return g_strdup( filefrag + 4); - } - - /* get conservative on the length so that sprintf(getpid()) works ... */ /* strlen ("/.LCK") + sprintf (%x%d) */ namelen = strlen (filefrag) + 25; @@ -183,6 +170,7 @@ xaccResolveFilePath (const char * filefrag) fullpath = gnc_build_data_path(filefrag); if (g_file_test(fullpath, G_FILE_TEST_IS_REGULAR)) return fullpath; + /* OK, it's not there. Note that it needs to be created and pass it * back anyway */ g_warning("create new file %s", fullpath); @@ -192,89 +180,6 @@ xaccResolveFilePath (const char * filefrag) /* ====================================================================== */ -/** @fn char * xaccResolveURL (const char * pathfrag) - * - * @brief Return the passed-in string unless it starts with file:, - * xml:, or is a raw relative path. - * - * Strings starting with http://, https://, or a "registered scheme" - * (see qof_backend_get_registered_access_method_list()) are returned - * as-is by this function. - * - * Strings which form an absolute path (as determined by - * g_path_is_absolute()) are passed to xaccResolveFilePath() and - * immediately returned as-is. - * - * Strings which begin with file: or xml: are passed to - * xaccResolveFilePath, which strips off the "scheme" part (file: or - * xml: plus // if present) and returns the rest unchanged. This - * result is passed back to the caller as-is if the original astring - * started with file:; if it started with xml:, then xml: is prepended - * before passing it back to the caller. Note that this has the effect - * of converting a URI of the form xml:///path/to/file into one of the - * form xml:/path/to/file. - * - * Strings which meet none of the above are passed to - * xaccResolveFilePath and the result returned to the caller. - * - * @param pathfrag the string to "resolve" - * - * @return "resolved" string. - */ -char * -xaccResolveURL (const char * pathfrag) -{ - GList* list; - GList* node; - - /* seriously invalid */ - if (!pathfrag) return NULL; - - /* At this stage of checking, URL's are always, by definition, - * resolved. If there's an error connecting, we'll find out later. - */ - - if (!g_ascii_strncasecmp (pathfrag, "http://", 7) || - !g_ascii_strncasecmp (pathfrag, "https://", 8)) - { - return g_strdup(pathfrag); - } - - /* Check the URL against the list of registered access methods */ - list = qof_backend_get_registered_access_method_list(); - for ( node = list; node != NULL; node = node->next ) - { - const gchar* access_method = node->data; - if ( strcmp( access_method, "file" ) != 0 && - strcmp( access_method, "xml" ) != 0 ) - { - gchar s[30]; - sprintf( s, "%s://", access_method ); - if ( !g_ascii_strncasecmp( pathfrag, s, strlen(s) ) ) - { - g_list_free(list); - return g_strdup(pathfrag); - } - } - } - g_list_free(list); - /* - * xml: schemes are a special case, becuase gnc_file_do_save_as() - * relies on the phony scheme id being present in the returned path - * to distinguish the backend used to save the file. Note that this - * has the amusing effect of stripping the // from the phony scheme, - * so if it started out as xml:///path/to/data, it gets returned - * from here as xml:/path/to/data. - */ - if (!g_ascii_strncasecmp (pathfrag, "xml:", 4)) - { - return (g_strdup_printf( "xml:%s", xaccResolveFilePath (pathfrag)) ); - } - return (xaccResolveFilePath (pathfrag)); -} - -/* ====================================================================== */ - /** @fn void gnc_validate_directory (const gchar *dirname) * @brief Check that the supplied directory path exists, is a directory, and that the user has adequate permissions to use it. * diff --git a/src/core-utils/gnc-filepath-utils.h b/src/core-utils/gnc-filepath-utils.h index 77e8d466bf..9b7eb8999f 100644 --- a/src/core-utils/gnc-filepath-utils.h +++ b/src/core-utils/gnc-filepath-utils.h @@ -1,5 +1,5 @@ /********************************************************************\ - * gnc-filepath-utils.h -- file path resolutin utilitie * + * gnc-filepath-utils.h -- file path resolution utilities * * * * This program is free software; you can redistribute it and/or * * modify it under the terms of the GNU General Public License as * @@ -39,8 +39,7 @@ * $HOME is not defined, then the current working directory is * used. */ -char * xaccResolveFilePath (const char * filefrag); -char * xaccResolveURL (const char * pathfrag); +gchar *gnc_resolve_file_path (const gchar *filefrag); const gchar *gnc_dotgnucash_dir (void); gchar *gnc_build_dotgnucash_path (const gchar *filename); diff --git a/src/core-utils/gnc-uri-utils.c b/src/core-utils/gnc-uri-utils.c new file mode 100644 index 0000000000..339606e72d --- /dev/null +++ b/src/core-utils/gnc-uri-utils.c @@ -0,0 +1,272 @@ +/* + * gnc-uri-utils.c -- utility functions to convert uri in separate + * components and back. + * + * Copyright (C) 2010 Geert Janssens + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA gnu@gnu.org + */ + +#include +#include "gnc-uri-utils.h" +#include "gnc-filepath-utils.h" + +/* Checks if the given protocol is used to refer to a file + * (as opposed to a network service) + */ +gboolean gnc_uri_is_file_protocol (const gchar *protocol) +{ + if ( !g_ascii_strcasecmp (protocol, "file") || + !g_ascii_strcasecmp (protocol, "xml") || + !g_ascii_strcasecmp (protocol, "sqlite3") + ) + return TRUE; + else + return FALSE; +} + +/* Checks if the given uri defines a file + * (as opposed to a network service) + */ +gboolean gnc_uri_is_file_uri (const gchar *uri) +{ + gchar *protocol = gnc_uri_get_protocol ( uri ); + gboolean result = gnc_uri_is_file_protocol ( protocol ); + + g_free ( protocol ); + + return result; +} + +/* Splits a uri into its separate components */ +void gnc_uri_get_components (const gchar *uri, + gchar **protocol, + gchar **hostname, + guint32 port, + gchar **username, + gchar **password, + gchar **path) +{ + gchar **splituri, **spliturl; + gchar *url = NULL, *tmpusername = NULL, *tmphostname = NULL; + gchar *delimiter = NULL; + + *protocol = NULL; + *hostname = NULL; + port = 0; + *username = NULL; + *password = NULL; + *path = NULL; + + g_return_if_fail( uri != 0 ); + + splituri = g_strsplit ( uri, "://", 2 ); + if ( splituri[1] == NULL ) + { + /* No protocol means simple file uri */ + *protocol = g_strdup ( "file" ); + *path = g_strdup ( splituri[0] ); + g_strfreev ( splituri ); + return; + } + + /* At least a protocol was found, set it here */ + *protocol = g_strdup ( splituri[0] ); + + if ( gnc_uri_is_file_protocol ( *protocol ) ) + { + /* Protocol indicates file based uri */ + *path = gnc_resolve_file_path ( splituri[1] ); + g_strfreev ( splituri ); + return; + } + + /* Protocol indicates full network style uri, let's see if it + * has a username and/or password + */ + url = g_strdup (splituri[1]); + g_strfreev ( splituri ); + + delimiter = g_strrstr ( url, "@" ); + if ( delimiter != NULL ) + { + /* There is at least a username in the url */ + delimiter[0] = '\0'; + tmpusername = url; + tmphostname = delimiter + 1; + + /* Check if there's a password too */ + delimiter = g_strstr_len ( tmpusername, -1, ":" ); + if ( delimiter != NULL ) + { + /* There is password in the url */ + delimiter[0] = '\0'; + *username = g_strdup ( (const gchar*)tmpusername ); + *password = g_strdup ( (const gchar*)(delimiter+1) ); + } + } + else + { + /* No username and password were given */ + tmphostname = url; + } + + /* Find the path part */ + delimiter = g_strstr_len ( tmphostname, -1, "/" ); + if ( delimiter != NULL ) + { + delimiter[0] = '\0'; + if ( gnc_uri_is_file_protocol ( *protocol ) ) /* always return absolute file paths */ + *path = gnc_resolve_file_path ( (const gchar*)(delimiter+1) ); + else /* path is no file path, so copy it as is */ + *path = g_strdup ( (const gchar*)(delimiter+1) ); + } + + /* Check for a port specifier */ + delimiter = g_strstr_len ( tmphostname, -1, ":" ); + if ( delimiter != NULL ) + { + delimiter[0] = '\0'; + port = g_ascii_strtoll ( (const gchar*)(delimiter+1), NULL, 0 ); + } + + *hostname = g_strdup ( (const gchar*)tmphostname ); + + g_free ( url ); + + return; + +} + +gchar *gnc_uri_get_protocol (const gchar *uri) +{ + gchar *protocol = NULL; + gchar *hostname = NULL; + guint32 port = 0; + gchar *username = NULL; + gchar *password = NULL; + gchar *path = NULL; + + gnc_uri_get_components ( uri, &protocol, &hostname, port, + &username, &password, &path ); + + g_free (hostname); + g_free (username); + g_free (password); + g_free (path); + + return protocol; +} + +gchar *gnc_uri_get_path (const gchar *uri) +{ + gchar *protocol = NULL; + gchar *hostname = NULL; + guint32 port = 0; + gchar *username = NULL; + gchar *password = NULL; + gchar *path = NULL; + + gnc_uri_get_components ( uri, &protocol, &hostname, port, + &username, &password, &path ); + + g_free (protocol); + g_free (hostname); + g_free (username); + g_free (password); + + return path; +} + +/* Generates a normalized uri from the separate components */ +gchar *gnc_uri_create_uri (const gchar *protocol, + const gchar *hostname, + guint32 port, + const gchar *username, + const gchar *password, + const gchar *path) +{ + gchar *userpass=NULL, *uri=NULL; + + g_return_val_if_fail( path != 0, NULL ); + + if ( (protocol == NULL) || gnc_uri_is_file_protocol ( protocol ) ) + { + /* Compose a file based uri, which means ignore everything but + * the protocol and the path + * We always return absolute pathnames + */ + gchar *abs_path = gnc_resolve_file_path ( path ); + if ( protocol == NULL ) + uri = g_strdup_printf ( "file://%s", abs_path ); + else + uri = g_strdup_printf ( "%s://%s", protocol, abs_path ); + g_free (abs_path); + return uri; + } + + /* Not a file based uri, we need to setup all components that are not NULL + * For this scenario, hostname is mandatory. + */ + g_return_val_if_fail( hostname != 0, NULL ); + + if ( username != NULL ) + { + if ( password != NULL ) + userpass = g_strdup_printf ( "%s:%s@", username, password); + else + userpass = g_strdup_printf ( "%s@", username); + } + + // XXX Do I have to add the slash always or are there situations + // it is in the path already ? + uri = g_strconcat ( protocol, "://", userpass, hostname, "/", path, NULL ); + + g_free ( userpass ); + + return uri; + +} + +gchar *gnc_uri_normalize_uri (const gchar *uri, gboolean allow_password) +{ + gchar *protocol = NULL; + gchar *hostname = NULL; + guint32 port = 0; + gchar *username = NULL; + gchar *password = NULL; + gchar *path = NULL; + gchar *newuri = NULL; + + gnc_uri_get_components ( uri, &protocol, &hostname, port, + &username, &password, &path ); + if (allow_password) + newuri = gnc_uri_create_uri ( protocol, hostname, port, + username, password, path); + else + newuri = gnc_uri_create_uri ( protocol, hostname, port, + username, /* no password */ NULL, path); + + g_free (protocol); + g_free (hostname); + g_free (username); + g_free (password); + g_free (path); + + return newuri; +} diff --git a/src/core-utils/gnc-uri-utils.h b/src/core-utils/gnc-uri-utils.h new file mode 100644 index 0000000000..340f43443e --- /dev/null +++ b/src/core-utils/gnc-uri-utils.h @@ -0,0 +1,205 @@ +/* + * gnc-uri-utils.h -- utility functions to convert uri in separate + * components and back. + * + * Copyright (C) 2010 Geert Janssens + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA gnu@gnu.org + */ + +/** @addtogroup Utils Utility functions + @{ */ +/** @addtogroup UtilUri Uri conversion + * @{ */ +/** @file gnc-uri-utils.h + * @brief Utility functions for convert uri in separate components and back + * @author Copyright (C) 2010 Geert Janssens + * + * These functions help you convert a uri into its separate components + * (being protocol, host name, port, user name, password and path) or + * to compose a uri from these separate components. + * + */ + +#ifndef GNCURIUTILS_H_ +#define GNCURIUTILS_H_ + +/** Converts a uri in separate components. + * + * Uri's can take any of the following forms: + * + * @li @c /some/filesystem/path A simple file system path (unix style) + * @li @c c:\\some\windows\path A simple file system path (Windows style) + * @li @c proto://[[username[:password]@]hostname[:port]]/path (universal uri) + * + * In the last form, anything in square brackets is optional. + * + * The function allocates memory for each of the components that it finds + * in the uri. The calling function should free this memory with g_free + * if the items are no longer needed. + * + * @param uri The uri to convert + * + * @param protocol The protocol for this uri. If the uri didn't have an + * explicit protocol, 'file' will be the assumed protocol and hence what + * will be returned. + * @param hostname The host name of the server to connect to. In case of + * the 'file' protocol, this will be NULL + * @param port An optional port to connect to or 0 if the default port is to + * be used. For the 'file' protocol this is always 0 as well. + * @username Optional user name found in this uri or NULL if none is found. + * @password Optional password found in this uri or NULL if none is found. + * @path The path found in this uri. Note that if the protocol is a file based + * protocol, the path will be converted to an absolute path. + * + */ + +void gnc_uri_get_components (const gchar *uri, + gchar **protocol, + gchar **hostname, + guint32 port, + gchar **username, + gchar **password, + gchar **path); + +/** Extracts the protocol from a uri + * + * Uri's can take any of the following forms: + * + * @li @c /some/filesystem/path A simple file system path (unix style) + * @li @c c:\\some\windows\path A simple file system path (Windows style) + * @li @c proto://[[username[:password]@]hostname[:port]]/path (universal uri) + * + * In the last form, anything in square brackets is optional. + * + * The function allocates memory for the protocol. The calling function should + * free this memory with g_free if it no longer needs the string. + * + * @param uri The uri to extract the protocol from + * + * @return The protocol for this uri. If the uri didn't have an + * explicit protocol, 'file' will be returned as protocol. + */ + +gchar *gnc_uri_get_protocol (const gchar *uri); + +/** Extracts the path part from a uri + * + * Uri's can take any of the following forms: + * + * @li @c /some/filesystem/path A simple file system path (unix style) + * @li @c c:\\some\windows\path A simple file system path (Windows style) + * @li @c proto://[[username[:password]@]hostname[:port]]/path (universal uri) + * + * In the last form, anything in square brackets is optional. + * + * The function allocates memory for the path. The calling function should + * free this memory with g_free if it no longer needs the string. + * + * @param uri The uri to extract the path part from + * + * @return The protocol for this uri, or NULL if no path could be extracted. + */ + +gchar *gnc_uri_get_path (const gchar *uri); + +/** Composes a normalized uri starting from its separate components. + * + * The resulting uri will take either of these forms: + * @li @c file:///some/absolute/path (file could also be xml or sqlite) + * @li @c file://c:\\some\\windows\\path (file could also be xml or sqlite) + * @li @c protocol://[user[:password]@]hostname[:port]/path + * + * Only the components that are provided will be inserted in the uri. However + * if no protocol has been provided, 'file' will be used as default protocol. + * + * The function allocates memory for for the uri. The calling function should + * free this memory with g_free the uri is no longer needed. + * +* @param protocol The protocol for this uri. If NULL,, 'file' will be used +* in the uri. + * @param hostname The host name of the server to connect to. This will be + * ignored for the 'file' type protocols ('file', 'xml', 'sqlite'). + * @param port An optional port to set o, the uri, or 0 if no port is to be + * set. This will be ignored for the 'file' type protocols ('file', 'xml', + * 'sqlite'). + * @username Optional user name to set in the uri or NULL otherwise. This will + * be ignored for the 'file' type protocols ('file', 'xml', 'sqlite'). + * @password Optional password to set in the uri or NULL otherwise. This will + * be ignored for the 'file' type protocols ('file', 'xml', 'sqlite'). + * @path The path to set in the uri. + * + * @return The normalized uri. + */ + +gchar *gnc_uri_create_uri (const gchar *protocol, + const gchar *hostname, + guint32 port, + const gchar *username, + const gchar *password, + const gchar *path); + +/** Composes a normalized uri starting from any uri (filename, db spec,...). + * + * The resulting uri will take either of these forms: + * @li @c file:///some/absolute/path (file could also be xml or sqlite) + * @li @c file://c:\\some\\windows\\path (file could also be xml or sqlite) + * @li @c protocol://[user[:password]@]hostname[:port]/path + * + * Only the components that are provided will be inserted in the uri. The + * allow_password parameter controls if the password should be added to the + * returned uri when available. + * If no protocol has been provided, 'file' will be used as default protocol. + * + * The function allocates memory for for the uri. The calling function should + * free this memory with g_free the uri is no longer needed. + * +* @param uri The uri that schould be converted into a normalized uri + * @param allow_password If set to TRUE, the normalized uri and the input uri + * has a password, this passworld will also be set in the normalized uri. + * Otherwise no password will be set in the normalized uri. + * + * @return The normalized uri. + */ +gchar *gnc_uri_normalize_uri (const gchar *uri, gboolean allow_password); + + +/** Checks if the given protocol is used to refer to a file + * (as opposed to a network service like a database or web url) + * + * @param protocol The protocol to check + * + * @return TRUE if the protocol is used with files, FALSE of the protocol + * is normally used with network services (database, web url,...) + */ +gboolean gnc_uri_is_file_protocol (const gchar *protocol); + +/** Checks if the given uri defines a file + * (as opposed to a network service like a database or web url) + * + * @param uri The uri to check + * + * @return TRUE if the uri is a files, FALSE of the protocol + * is normally used with network services (database, web url,...) + */ +gboolean gnc_uri_is_file_uri (const gchar *uri); + +#endif /* GNCURIUTILS_H_ */ +/** @} */ +/** @} */ + diff --git a/src/gnome-utils/Makefile.am b/src/gnome-utils/Makefile.am index d9dc52e40f..74db344d5b 100644 --- a/src/gnome-utils/Makefile.am +++ b/src/gnome-utils/Makefile.am @@ -18,6 +18,7 @@ AM_CPPFLAGS = \ ${GLADE_CFLAGS} \ ${GTK_CFLAGS} \ ${GNOME_CFLAGS} \ + ${GNOME_KEYRING_CFLAGS} \ ${GUILE_INCS} \ ${QOF_CFLAGS} \ ${GOFFICE_CFLAGS} \ @@ -68,6 +69,7 @@ libgncmod_gnome_utils_la_SOURCES = \ gnc-gtk-utils.c \ gnc-gui-query.c \ gnc-icons.c \ + gnc-keyring.c \ gnc-main-window.c \ gnc-menu-extensions.c \ gnc-plugin-file-history.c \ @@ -136,6 +138,7 @@ gncinclude_HEADERS = \ gnc-gnome-utils.h \ gnc-gui-query.h \ gnc-icons.h \ + gnc-keyring.h \ gnc-main-window.h \ gnc-menu-extensions.h \ gnc-plugin-file-history.h \ @@ -186,6 +189,7 @@ libgncmod_gnome_utils_la_LIBADD = \ $(top_builddir)/lib/libc/libc-missing.la \ ${top_builddir}/src/libqof/qof/libgnc-qof.la \ ${GNOME_LIBS} \ + ${GNOME_KEYRING_LIBS} \ ${GLADE_LIBS} \ ${GUILE_LIBS} \ ${GLIB_LIBS} \ diff --git a/src/gnome-utils/dialog-file-access.c b/src/gnome-utils/dialog-file-access.c index cb5de6ce2b..a0db6e03fc 100644 --- a/src/gnome-utils/dialog-file-access.c +++ b/src/gnome-utils/dialog-file-access.c @@ -29,6 +29,7 @@ #include #include "gnc-ui.h" +#include "gnc-uri-utils.h" #include "dialog-utils.h" #include "dialog-file-access.h" #include "gnc-file.h" @@ -63,13 +64,14 @@ typedef struct FileAccessWindow static gchar* geturl( FileAccessWindow* faw ) { - gchar* url; + gchar* url = NULL; const gchar* host; const gchar* database; const gchar* username; const gchar* password; const gchar* type; const gchar* file; + const gchar* path; host = gtk_entry_get_text( faw->tf_host ); database = gtk_entry_get_text( faw->tf_database ); @@ -78,36 +80,17 @@ geturl( FileAccessWindow* faw ) file = gtk_file_chooser_get_filename( faw->fileChooser ); type = gtk_combo_box_get_active_text( faw->cb_uri_type ); - if ( (( strcmp( type, "xml" ) == 0 ) || - ( strcmp( type, "sqlite3" ) == 0 ) || - ( strcmp( type, "file" ) == 0 )) && - ( file == NULL ) ) - return NULL; + if ( gnc_uri_is_file_protocol( type ) ) + { + if ( file == NULL ) /* file protocol was chosen but no filename was set */ + return NULL; + else /* file protocol was chosen with filename set */ + path = file; + } + else /* db protocol was chosen */ + path = database; - if ( strcmp( type, "xml" ) == 0 ) - { - url = g_strdup_printf( "xml://%s", file ); - } - else if ( strcmp( type, "sqlite3" ) == 0 ) - { - url = g_strdup_printf( "sqlite3://%s", file ); - } - else if ( strcmp( type, "file" ) == 0 ) - { - url = g_strdup_printf( "file://%s", file ); - } - else if ( strcmp( type, "mysql" ) == 0 ) - { - url = g_strdup_printf( "mysql://%s:%s:%s:%s", - host, database, username, password ); - } - else - { - g_assert( strcmp( type, "postgres" ) == 0 ); - type = "postgres"; - url = g_strdup_printf( "postgres://%s:%s:%s:%s", - host, database, username, password ); - } + url = gnc_uri_create_uri (type, host, 0, username, password, path); return url; } diff --git a/src/gnome-utils/druid-gnc-xml-import.c b/src/gnome-utils/druid-gnc-xml-import.c index 61c9abd449..d2f9df684d 100644 --- a/src/gnome-utils/druid-gnc-xml-import.c +++ b/src/gnome-utils/druid-gnc-xml-import.c @@ -31,7 +31,7 @@ #include "dialog-utils.h" #include "druid-utils.h" #include "gnc-backend-xml.h" -#include "gnc-filepath-utils.h" +#include "gnc-uri-utils.h" #include "gnc-module.h" #include "gnc-ui.h" #include "io-gncxml-v2.h" @@ -273,7 +273,7 @@ gnc_xml_convert_single_file (const gchar *filename) data = g_new0 (GncXmlImportData, 1); data->import_type = XML_CONVERT_SINGLE_FILE; - data->filename = g_strdup (filename); + data->filename = gnc_uri_get_path (filename); /* gather ambiguous info */ gxi_check_file (data); @@ -751,7 +751,7 @@ gxi_parse_file (GncXmlImportData *data) goto cleanup_parse_file; } - logpath = xaccResolveFilePath (data->filename); + logpath = gnc_uri_get_path (data->filename); xaccLogSetBaseName (logpath); xaccLogDisable (); gxi_update_progress_bar (_("Reading file..."), 0.0); diff --git a/src/gnome-utils/gnc-file.c b/src/gnome-utils/gnc-file.c index 6ae662e73d..af14f43eb5 100644 --- a/src/gnome-utils/gnc-file.c +++ b/src/gnome-utils/gnc-file.c @@ -33,12 +33,12 @@ #include "gnc-component-manager.h" #include "gnc-engine.h" #include "gnc-file.h" -#include "gnc-filepath-utils.h" #include "gnc-gui-query.h" #include "gnc-hooks.h" #include "gnc-splash.h" #include "gnc-ui.h" #include "gnc-ui-util.h" +#include "gnc-uri-utils.h" #include "gnc-window.h" #include "gnc-plugin-file-history.h" #include "qof.h" @@ -497,23 +497,21 @@ show_session_error (QofBackendError io_error, static void gnc_add_history (QofSession * session) { - char *url; + const gchar *url; char *file; if (!session) return; - url = xaccResolveURL (qof_session_get_url (session)); - if (!url) + url = qof_session_get_url ( session ); + if ( !url ) return; - if (strncmp (url, "file:", 5) == 0) - file = url + 5; + if ( gnc_uri_is_file_uri ( url ) ) + file = gnc_uri_get_path ( url ); else - file = url; + file = gnc_uri_normalize_uri ( url, TRUE ); /* FIXME this saves the password visibly in history ! */ gnc_history_add_file (file); - - g_free (url); } static void @@ -646,7 +644,10 @@ gnc_post_file_open (const char * filename) if (!filename) return FALSE; - newfile = xaccResolveURL (filename); + /* FIXME Verify if it is ok that a password is stored + * in the uri here. + */ + newfile = gnc_uri_normalize_uri ( filename, TRUE ); if (!newfile) { show_session_error (ERR_FILEIO_FILE_NOT_FOUND, filename, @@ -773,12 +774,20 @@ gnc_post_file_open (const char * filename) if (!uh_oh) { Account *new_root; + gchar *logpath = NULL; - char * logpath = xaccResolveFilePath(newfile); + /* XXX Would logging make sense for databases as well (mysql/postgres) ? + * Currently the logpath is relative to the data file path. + * Databases don't have a file path, so no logging will be + * done for them in the current setup. + */ + if ( gnc_uri_is_file_uri ( newfile ) ) + logpath = gnc_uri_get_path(newfile); PINFO ("logpath=%s", logpath ? logpath : "(null)"); xaccLogSetBaseName (logpath); - xaccLogDisable(); + g_free ( logpath ); + xaccLogDisable(); gnc_window_show_progress(_("Loading user data..."), 0.0); qof_session_load (new_session, gnc_window_show_progress); gnc_window_show_progress(NULL, -1.0); @@ -864,26 +873,34 @@ gnc_post_file_open (const char * filename) return TRUE; } +/* Routine that pops up a file chooser dialog + * + * Note: this dialog is used when dbi is not enabled + * so the paths used in here are always file + * paths, never db uris. + */ gboolean gnc_file_open (void) { const char * newfile; - char *lastfile = NULL; + gchar *lastpath = NULL; + gchar *lastfile = NULL; gchar *last_file_dir = NULL; gboolean result; if (!gnc_file_query_save (TRUE)) return FALSE; - lastfile = gnc_history_get_last(); - if (lastfile) + lastpath = gnc_history_get_last(); + lastfile = gnc_uri_get_path ( lastpath ); + if ( lastfile ) last_file_dir = g_path_get_dirname(lastfile); newfile = gnc_file_dialog (_("Open"), NULL, last_file_dir, GNC_FILE_DIALOG_OPEN); - if (lastfile != NULL) - g_free(lastfile); - if (last_file_dir != NULL) - g_free(last_file_dir); - result = gnc_post_file_open (newfile); + g_free ( lastpath ); + g_free ( lastfile ); + g_free ( last_file_dir ); + + result = gnc_post_file_open ( newfile ); /* This dialogue can show up early in the startup process. If the * user fails to pick a file (by e.g. hitting the cancel button), we @@ -1037,6 +1054,10 @@ gnc_file_save (void) LEAVE (" "); } +/* Note: this dialog will only be used when dbi is not enabled + * paths used in it always refer to files and are + * never db uris + */ void gnc_file_save_as (void) { @@ -1052,10 +1073,11 @@ gnc_file_save_as (void) ENTER(" "); last = gnc_history_get_last(); - if (last) + if ( last && gnc_uri_is_file_uri ( last ) ) { - default_dir = g_path_get_dirname(last); - g_free(last); + gchar *filepath = gnc_uri_get_path ( last ); + default_dir = g_path_get_dirname( filepath ); + g_free ( filepath ); } else { @@ -1063,7 +1085,8 @@ gnc_file_save_as (void) } filename = gnc_file_dialog (_("Save"), NULL, default_dir, GNC_FILE_DIALOG_SAVE); - g_free(default_dir); + g_free ( last ); + g_free ( default_dir ); if (!filename) return; gnc_file_do_save_as( filename ); @@ -1080,14 +1103,16 @@ gnc_file_do_save_as (const char* filename) char *last; char *newfile; const char *oldfile; + gchar *logpath = NULL; + QofBackendError io_err = ERR_BACKEND_NO_ERR; ENTER(" "); /* Check to see if the user specified the same file as the current - * file. If so, then just do that, instead of the below, which - * assumes a truly new name was given. */ - newfile = xaccResolveURL (filename); + * file. If so, then just do a simple save, instead of a full save as */ + /* FIXME Check if it is ok to have a password in the uri here */ + newfile = gnc_uri_normalize_uri ( filename, TRUE ); if (!newfile) { show_session_error (ERR_FILEIO_FILE_NOT_FOUND, filename, @@ -1109,7 +1134,6 @@ gnc_file_do_save_as (const char* filename) /* -- this session code is NOT identical in FileOpen and FileSaveAs -- */ - xaccLogSetBaseName(newfile); //FIXME: This is premature. save_in_progress++; new_session = qof_session_new (); qof_session_begin (new_session, newfile, FALSE, FALSE); @@ -1154,6 +1178,18 @@ gnc_file_do_save_as (const char* filename) return; } + /* XXX Would logging make sense for databases as well (mysql/postgres) ? + * Currently the logpath is relative to the data file path. + * Databases don't have a file path, so no logging will be + * done for them in the current setup. + */ + if ( gnc_uri_is_file_uri ( newfile ) ) + logpath = gnc_uri_get_path(newfile); + PINFO ("logpath=%s", logpath ? logpath : "(null)"); + xaccLogSetBaseName (logpath); + g_free ( logpath ); + + /* Prevent race condition between swapping the contents of the two * sessions, and actually installing the new session as the current * one. Any event callbacks that occur in this interval will have diff --git a/src/gnome-utils/gnc-keyring.c b/src/gnome-utils/gnc-keyring.c new file mode 100644 index 0000000000..e54af9bdb6 --- /dev/null +++ b/src/gnome-utils/gnc-keyring.c @@ -0,0 +1,231 @@ +/* + * gnc-keyring.c -- utility functions to store and retrieve passwords. + * + * Copyright (C) 2010 Geert Janssens + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA gnu@gnu.org + */ + +#include "config.h" +#include +#include "qof.h" +#include "gnc-ui.h" +#include "gnc-keyring.h" +#ifdef HAVE_GNOME_KEYRING +#include +#endif +#ifdef HAVE_OSX_KEYCHAIN +#include +#endif + +/* This static indicates the debugging module that this .o belongs to. */ +static QofLogModule log_module = GNC_MOD_GUI; + +void gnc_keyring_set_password (const gchar *access_method, + const gchar *server, + guint32 port, + const gchar *service, + const gchar *user, + const gchar* password) +{ + +#ifdef HAVE_GNOME_KEYRING + GnomeKeyringResult gkr_result; + guint32 *item_id = NULL; + + gkr_result = gnome_keyring_set_network_password_sync + (NULL, user, NULL, server, service, + access_method, NULL, port, password, item_id); + + if (gkr_result != GNOME_KEYRING_RESULT_OK) + { + PWARN ("Gnome-keyring error: %s", + gnome_keyring_result_to_message(gkr_result)); + PWARN ("The user will be prompted for a password again next time."); + } +#endif /* HAVE_GNOME_KEYRING */ +#ifdef HAVE_OSX_KEYCHAIN +# ifdef 0 + /* FIXME The OSX part hasn't been tested yet */ + OSStatus status; + SecKeychainItemRef *itemRef = NULL; + + /* mysql and postgres aren't valid protocols on Mac OS X. + * So we use the security domain parameter to allow us to + * distinguish between these two. + */ + // FIXME I'm not sure this works if a password was already in the keychain + // I may have to do a lookup first and if it exists, run some update + // update function instead + g_set_application_name(PACKAGE); + status = SecKeychainAddInternetPassword ( NULL, /* keychain */ + strlen(server), server, /* servername */ + strlen(access_method), access_method,/* securitydomain */ + strlen(*user), *user, /* acountname */ + strlen(service), service, /* path */ + port, /* port */ + kSecProtocolTypeAny, /* protocol */ + kSecAuthenticationTypeDefault, /* auth type */ + strlen(password), password, /* passworddata */ + SecKeychainItemRef *itemRef ); + + if ( status != noErr ) + { + CFStringRef resultstring = SecCopyErrorMessageString( status, NULL ); + PWARN ( "OS X keychain error: %s", resultstring ); + PWARN ( "The user will be prompted for a password again next time." ); + CFRelease ( resultstring ); + } + + +# endif /* 0 */ +#endif /* HAVE_OSX_KEYCHAIN */ +} + + +gboolean gnc_keyring_get_password ( GtkWidget *parent, + const gchar *access_method, + const gchar *server, + guint32 port, + const gchar *service, + gchar **user, + gchar **password) +{ + gboolean password_found = FALSE; +#ifdef HAVE_GNOME_KEYRING + GnomeKeyringResult gkr_result; + GList *found_list = NULL; + GnomeKeyringNetworkPasswordData *found; +#endif +#ifdef HAVE_OSX_KEYCHAIN +# ifdef 0 + /* FIXME The OSX part hasn't been tested yet */ + void *password_data; + UInt32 password_length; + OSStatus status; +# endif /* 0 */ +#endif + + g_return_val_if_fail (user != NULL, FALSE); + g_return_val_if_fail (password != NULL, FALSE); + + *password = NULL; + +#ifdef HAVE_GNOME_KEYRING + g_set_application_name(PACKAGE); + gkr_result = gnome_keyring_find_network_password_sync + ( *user, NULL, server, service, + access_method, NULL, port, &found_list ); + + if (gkr_result == GNOME_KEYRING_RESULT_OK) + { + found = (GnomeKeyringNetworkPasswordData *) found_list->data; + if (found->password) + *password = g_strdup(found->password); + password_found = TRUE; + } + else + PWARN ("Gnome-keyring access failed: %s.", + gnome_keyring_result_to_message(gkr_result)); + + gnome_keyring_network_password_list_free(found_list); +#endif /* HAVE_GNOME_KEYRING */ +#ifdef HAVE_OSX_KEYCHAIN +# ifdef 0 + /* FIXME The OSX part hasn't been tested yet */ + void *password_data; + UInt32 password_length; + OSStatus status; + + /* mysql and postgres aren't valid protocols on Mac OS X. + * So we use the security domain parameter to allow us to + * distinguish between these two. + */ + status = SecKeychainFindInternetPassword( NULL, + strlen(server), server, + strlen(access_method), access_method, + strlen(*user), *user, + strlen(service), service, + port, + kSecProtocolTypeAny, + kSecAuthenticationTypeDefault, + &password_length, &password_data, + NULL); + + if ( status == noErr ) + { + *password = xmalloc((password_length + 1) * sizeof(char)); + strncpy(*password, password_data, (size_t)password_length); + (*password)[password_length] = '\0'; + password_found = TRUE; + SecKeychainItemFreeContent(NULL, password_data); + } + else + { + CFStringRef resultstring = SecCopyErrorMessageString( status, NULL ); + PWARN ( "OS X keychain error: %s", resultstring ); + CFRelease ( resultstring ); + } + +# endif /* 0 */ +#endif /* HAVE_OSX_KEYCHAIN */ + + if ( !password_found ) + { + /* If we got here, either no proper password store is + * available on this system, or we couldn't retrieve + * a password from it. In both cases, just ask the user + * to enter one + */ + gchar *db_path, *heading; + + if ( port == 0 ) + db_path=g_strdup_printf ( "%s://%s/%s", access_method, server, service ); + else + db_path=g_strdup_printf ( "%s://%s:%d/%s", access_method, server, port, service ); + heading = g_strdup_printf ( /* Translators: %s is a path to a database or any other url, + like mysql://user@server.somewhere/somedb, http://www.somequotes.com/thequotes */ + _("Enter a user name and password to connect to: %s"), + db_path ); + + password_found = gnc_get_username_password ( parent, heading, + *user, NULL, + user, password ); + g_free ( db_path ); + g_free ( heading ); + + if ( password_found ) + { + /* User entered new user/password information + * Let's try to add it to a password store. + */ + gchar *newuser = g_strdup( *user ); + gchar *newpassword = g_strdup( *password ); + gnc_keyring_set_password ( access_method, + server, + port, + service, + newuser, + newpassword ); + g_free ( newuser ); + g_free ( newpassword ); + } + } + + return password_found; +} diff --git a/src/gnome-utils/gnc-keyring.h b/src/gnome-utils/gnc-keyring.h new file mode 100644 index 0000000000..cd5e3133d5 --- /dev/null +++ b/src/gnome-utils/gnc-keyring.h @@ -0,0 +1,127 @@ +/* + * gnc-keyring.h -- utility functions to store and retrieve passwords. + * + * Copyright (C) 2010 Geert Janssens + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of + * the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, contact: + * + * Free Software Foundation Voice: +1-617-542-5942 + * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 + * Boston, MA 02110-1301, USA gnu@gnu.org + */ + +/** @addtogroup GUI + @{ */ +/** @addtogroup GUIUtility + @{ */ +/** @file gnc-keyring.h + @brief Functions to save and retrieve passwords. + @author Copyright (C) 2010 Geert Janssens + + GnuCash needs passwords for some of the services it uses, like + connecting to a remote database, or bank. For security these passwords + shouldn't be stored unless in an encrypted password store. This is + implemented for example in Gnome's keyring or Mac OS X' keychain. + This file defines some convenience functions to store a password + or to retrieve one. +*/ +#ifndef KEYRING_H_ +#define KEYRING_H_ + +#include + +#include "gnc-ui.h" + +/** Attempt to store a password in some trusted keystore. At this point + * that can be Gnome's keyring or Mac OS X' keychain. If no + * such keystore is available, this function does nothing. + * + * All the parameters passed (except for the password) will be + * used to create a unique key, so the password can later be + * retrieved again with the same parameters. + * + * @param parent Used to transition from in case the user is prompted + * for a password. + * @param access_method Service type the user attempts to access. Can + * things like 'mysql', 'postgres' and so on. + * @param server Server the user wishes to connect to. + * @param port Port the service listens on. If set to 0, it will + * be ignored in the search for a password. + * @param service The service the user wishes to access on the server. + * This can be a database name or a path. + * @param user The username to access the service. Remember, although + * you pass it to search for the password, it can have + * changed when the function returns. + * @param password The password to access the service. + */ +void gnc_keyring_set_password ( const gchar *access_method, + const gchar *server, + guint32 port, + const gchar *service, + const gchar *user, + const gchar* password ); + +/** Attempt to retrieve a password to connect to + * a remote service. This is deliberately generic: the remote + * service can be a database, website, anything. + * + * If a trusted keystore infrastructure is found (such as the + * Gnome's keyring or Mac OS X' keychain) this infrastructure + * will be queried first. + * + * If no such infrastructure is available or the query didn't + * return a valid result, the user will be prompted for his + * password. + * + * @warning When the user is prompted for a password, he can also + * change the username. So whenever you call this function, read + * both the username and password values before you continue ! + * + * @param parent Used to transition from in case the user is prompted + * for a password. + * @param access_method Service type the user attempts to access. Can + * things like 'mysql', 'postgres' and so on. + * @param server Server the user wishes to connect to. + * @param port Port the service listens on. If set to 0, it will + * be ignored in the search for a password. + * @param service The service the user wishes to access on the server. + * This can be a database name or a path. + * @param user The user name to access the service. Remember, although + * you pass it to search for the password, it can have + * changed when the function returns. + * @param password The password to access the service. + * @return a boolean indicating whether or not a valid password + * has been retrieved. The function will return FALSE + * when the user explicitly cancels the password dialog or + * if it wasn't called properly. Otherwise it wil return + * TRUE. + * + * access_method, server, port, service and user will be the parameters + * passed to the trusted keystore (if available) to find the unique + * password for this service. + */ + +gboolean gnc_keyring_get_password ( GtkWidget *parent, + const gchar *access_method, + const gchar *server, + guint32 port, + const gchar *service, + gchar **user, + gchar **password ); + +/* @} */ +/* @} */ + + +#endif /* KEYRING_H_ */ diff --git a/src/gnome-utils/gnc-main-window.c b/src/gnome-utils/gnc-main-window.c index fbc94389b6..516b773386 100644 --- a/src/gnome-utils/gnc-main-window.c +++ b/src/gnome-utils/gnc-main-window.c @@ -59,6 +59,7 @@ #include "gnc-session.h" #include "gnc-ui.h" #include "gnc-ui-util.h" +#include "gnc-uri-utils.h" #include "gnc-version.h" #include "gnc-window.h" #include "gnc-main.h" @@ -1357,8 +1358,9 @@ gnc_main_window_generate_title (GncMainWindow *window) GncPluginPage *page; QofBook *book; gchar *filename = NULL; + const gchar *book_id = NULL; const gchar *dirty = ""; - gchar *title, *ptr; + gchar *title; GtkAction* action; /* The save action is sensitive if the book is dirty */ @@ -1369,7 +1371,7 @@ gnc_main_window_generate_title (GncMainWindow *window) } if (gnc_current_session_exist()) { - filename = (gchar*)gnc_session_get_url (gnc_get_current_session ()); + book_id = gnc_session_get_url (gnc_get_current_session ()); book = gnc_get_current_book(); if (qof_instance_is_dirty(QOF_INSTANCE(book))) { @@ -1381,60 +1383,37 @@ gnc_main_window_generate_title (GncMainWindow *window) } } - if (!filename) + if (!book_id) filename = g_strdup(_("Unsaved Book")); else { - gint num_colons = 0; - for (ptr = filename; *ptr; ptr = g_utf8_next_char(ptr)) - { - gunichar c = g_utf8_get_char(ptr); - if (c == ':') num_colons++; - } + gchar *protocol = NULL; + gchar *hostname = NULL; + gchar *username = NULL; + gchar *password = NULL; + gchar *path = NULL; + guint32 port = 0; - if (num_colons < 4) + gnc_uri_get_components (book_id, &protocol, &hostname, + port, &username, &password, &path); + + if ( gnc_uri_is_file_protocol ( (const gchar*) protocol ) ) { - /* The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */ - ptr = g_utf8_strrchr(filename, -1, G_DIR_SEPARATOR); - if (ptr != NULL) - filename = g_strdup(g_utf8_next_char(ptr)); + /* The filename is a true file. + * The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */ + filename = g_path_get_basename ( path ); } else { - const gchar* src = filename; - gboolean has_explicit_port = (num_colons == 5); - - filename = g_strdup(filename); - ptr = filename; - num_colons = 0; - - /* Loop and copy chars, converting username and password (after 3rd ':' (4th if there's - an explicit port number) to asterisks. */ - for ( ; *src; src = g_utf8_next_char(src)) - { - gunichar unichar; - - if (*src == ':' || - (!has_explicit_port && num_colons < 3) || - (has_explicit_port && num_colons < 4)) - { - unichar = g_utf8_get_char(src); - } - else - { - unichar = '*'; - } - ptr += g_unichar_to_utf8 (unichar, ptr); - if (unichar == '_') - { - ptr += g_unichar_to_utf8 ('_', ptr); - } - else if (unichar == ':') - { - num_colons++; - } - } + /* The filename is composed of database connection parameters. + * For this we will show access_method://username@database[:port] */ + filename = gnc_uri_normalize_uri (book_id, FALSE); } + g_free(protocol); + g_free(hostname); + g_free(username); + g_free(password); + g_free(path); } priv = GNC_MAIN_WINDOW_GET_PRIVATE(window); @@ -1450,7 +1429,7 @@ gnc_main_window_generate_title (GncMainWindow *window) { title = g_strdup_printf("%s%s - GnuCash", dirty, filename); } - g_free(filename); + g_free( filename ); return title; } diff --git a/src/gnome-utils/gnc-plugin-file-history.c b/src/gnome-utils/gnc-plugin-file-history.c index 23f05a4011..20b4739e24 100644 --- a/src/gnome-utils/gnc-plugin-file-history.c +++ b/src/gnome-utils/gnc-plugin-file-history.c @@ -43,6 +43,7 @@ #include "gnc-window.h" #include "gnc-engine.h" #include "gnc-gconf-utils.h" +#include "gnc-uri-utils.h" static GObjectClass *parent_class = NULL; @@ -290,85 +291,25 @@ gnc_history_get_last (void) static gchar * gnc_history_generate_label (int index, const gchar *filename) { - const gchar *src; - gchar *result, *dst; - gunichar unichar; + gchar *label, *result; - /* raw byte length, not num characters */ - result = g_malloc(strlen(filename) * 2); - - dst = result; - if (index < 10) - dst += g_sprintf(result, "_%d ", (index + 1) % 10); - - /* If the filename begins with "mysql://" or "postgres://", hide the - user name and password. Otherwise, it is a filename - hide everything - except the file name. */ - - if (g_ascii_strncasecmp(filename, "mysql://", 8) == 0 || - g_ascii_strncasecmp(filename, "postgres://", 11) == 0 ) + if ( gnc_uri_is_file_uri ( filename ) ) { - gint num_colons = 0; - gboolean has_explicit_port; - - /* Count the number of colons to see if there is an explicit port number or not */ - for (src = filename; *src; src = g_utf8_next_char(src)) - { - gunichar c = g_utf8_get_char(src); - if (c == ':') num_colons++; - } - has_explicit_port = (num_colons == 5); - num_colons = 0; - - /* Loop for all chars and copy from 'src' to 'dst'. While doing this, - convert username and password (after 2nd ':', 3rd if explicit port) to asterisks. */ - src = filename; - for ( ; *src; src = g_utf8_next_char(src)) - { - if (*src == ':' || - (!has_explicit_port && num_colons < 3) || - (has_explicit_port && num_colons < 4)) - { - unichar = g_utf8_get_char(src); - } - else - { - unichar = '*'; - } - dst += g_unichar_to_utf8 (unichar, dst); - if (unichar == '_') - { - dst += g_unichar_to_utf8 ('_', dst); - } - else if (unichar == ':') - { - num_colons++; - } - } + /* for file paths, only display the file name */ + gchar *filepath = gnc_uri_get_path ( filename ); + label = g_path_get_basename ( filepath ); + g_free ( filepath ); } else { - /* Find the filename portion of the path */ - src = g_utf8_strrchr(filename, -1, G_DIR_SEPARATOR); - if (src) - { - src = g_utf8_next_char(src); - - /* Fix up any underline characters so they aren't mistaken as - * command accelerator keys. */ - for ( ; *src; src = g_utf8_next_char(src)) - { - unichar = g_utf8_get_char(src); - dst += g_unichar_to_utf8 (unichar, dst); - - if (unichar == '_') - dst += g_unichar_to_utf8 ('_', dst); - } - } + /* for databases, display the full uri, except for the password */ + label = gnc_uri_normalize_uri ( filename, FALSE ); } - *dst = '\0'; + result = g_strdup_printf ( "_%d %s", (index + 1) % 10, label); + g_free ( label ); return result; + } diff --git a/src/gnome/top-level.c b/src/gnome/top-level.c index 9a925bdee4..7c3eaed778 100644 --- a/src/gnome/top-level.c +++ b/src/gnome/top-level.c @@ -42,7 +42,6 @@ #include "gnc-engine.h" #include "gnc-gconf-utils.h" #include "gnc-file.h" -#include "gnc-filepath-utils.h" #include "gnc-hooks.h" #include "gfec.h" #include "gnc-main-window.h" @@ -97,7 +96,7 @@ static QofLogModule log_module = GNC_MOD_GUI; location); \ return FALSE; \ } \ - + static gboolean gnc_html_register_url_cb (const char *location, const char *label, diff --git a/src/libqof/qof/qofsession.c b/src/libqof/qof/qofsession.c index 626cd351b0..a2ac7a4fa1 100644 --- a/src/libqof/qof/qofsession.c +++ b/src/libqof/qof/qofsession.c @@ -1142,6 +1142,7 @@ qof_session_begin (QofSession *session, const char * book_id, { char *p, *access_method, *msg; int err; + gchar **splituri; if (!session) return; @@ -1174,36 +1175,16 @@ qof_session_begin (QofSession *session, const char * book_id, /* Store the session URL */ session->book_id = g_strdup (book_id); - /* Look for something of the form of "file:/", "http://" or + /* Look for something of the form of "file://", "http://" or * "postgres://". Everything before the colon is the access * method. Load the first backend found for that access method. */ - p = strchr (book_id, ':'); - if (p) - { - access_method = g_strdup (book_id); - p = strchr (access_method, ':'); - *p = '\0'; - qof_session_load_backend(session, access_method); - g_free (access_method); -#ifdef G_OS_WIN32 - if (NULL == session->backend) - { - /* Clear the error condition of previous errors */ - qof_session_clear_error (session); - - /* On windows, a colon can be part of a normal filename. So if - no backend was found (which means the part before the colon - wasn't an access method), fall back to the file backend. */ - qof_session_load_backend(session, "file"); - } -#endif - } - else - { - /* If no colon found, assume it must be a file-path */ + splituri = g_strsplit ( book_id, "://", 2 ); + if ( splituri[1] == NULL ) /* no access method in the uri, use generic "file" backend */ qof_session_load_backend(session, "file"); - } + else /* access method found, load appropriate backend */ + qof_session_load_backend(session, splituri[0]); + g_strfreev ( splituri ); /* No backend was found. That's bad. */ if (NULL == session->backend)