Use a normalized uri format internally to refer to data stores.

Data stores for GC can be a file (xml or sqlite3) or a database
one some server (mysql or postgres).
Wherever it makes sense internally, data stores will be referred to
via a normalized uri:
protocol://user:password@host:port/path
Depending on the context and story type some of these parts are optional or unused.

To achieve this, a new utility interface has been setup:
gnc_uri_<xxx>_<yyy>
that can be used to manipulate the uris or convert from non-normalized
formats to normalized and back.
For example, when the user selects a file in the Open or Save As dialog,
gnc_uri_get_normalized_uri will convert the file into a normalized uri.
Or when the actual filename is needed this can be extracted with
gnc_uri_get_path.
You can also test if a uri defines a file or something else with
gnc_uri_is_file_uri.

For the complete documentation, see src/core-utils/gnc-uri-uitls.h

This commit installs gnc-uri-utils and modifies the source where it makes
sense to use its convenience functions. This concerns all functions that
had to deal with file access in some way or another, the history module
and the functions that generate the history menu list and the window titles.

Note that gnc-uri-utils replaces xaccResolveFilePath and xaccResolveUrl in all cases.
xaccResolveUrl has been removed, because gnc-uri-utils fully replaces its functionality.
xaccResolveFilePath is used internally in gnc-uri-utils to ensure an absolute path
is always returned (in case of a file uri, not for db uris). But it has been renamed to
gnc_resolve_file_path to be more consistent with the other functions.

Lastly, this commit also adds a first implementation to work with a keyring to
store and retrieve passwords, althoug

git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@18842 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
Geert Janssens 2010-03-05 20:15:31 +00:00
parent a007db0301
commit 6a6fe1690e
19 changed files with 1090 additions and 451 deletions

View File

@ -1104,6 +1104,17 @@ esac
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,

View File

@ -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

View File

@ -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 )

View File

@ -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

View File

@ -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

View File

@ -24,10 +24,6 @@
* @brief file path resolution utilities
* @author Copyright (c) 1998-2004 Linas Vepstas <linas@linas.org>
* @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 <errno.h>
#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.
*
* @warning filefrag should be a simple path fragment. It shouldn't
* contain xml:// or http:// or <whatever>:// other protocol specifiers.
*
* 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.
* path, return the argument.
*
* 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
* 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)
*
* 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
* @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.
*

View File

@ -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);

View File

@ -0,0 +1,272 @@
/*
* gnc-uri-utils.c -- utility functions to convert uri in separate
* components and back.
*
* Copyright (C) 2010 Geert Janssens <janssens.geert@telenet.be>
*
* 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 <glib.h>
#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;
}

View File

@ -0,0 +1,205 @@
/*
* gnc-uri-utils.h -- utility functions to convert uri in separate
* components and back.
*
* Copyright (C) 2010 Geert Janssens <janssens.geert@telenet.be>
*
* 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 <janssens-geert@telenet.be>
*
* 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_ */
/** @} */
/** @} */

View File

@ -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} \

View File

@ -29,6 +29,7 @@
#include <glade/glade.h>
#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 ) )
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;
}

View File

@ -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);

View File

@ -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

View File

@ -0,0 +1,231 @@
/*
* gnc-keyring.c -- utility functions to store and retrieve passwords.
*
* Copyright (C) 2010 Geert Janssens <janssens.geert@telenet.be>
*
* 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 <glib/gi18n.h>
#include "qof.h"
#include "gnc-ui.h"
#include "gnc-keyring.h"
#ifdef HAVE_GNOME_KEYRING
#include <gnome-keyring.h>
#endif
#ifdef HAVE_OSX_KEYCHAIN
#include <SecKeychain.h>
#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;
}

View File

@ -0,0 +1,127 @@
/*
* gnc-keyring.h -- utility functions to store and retrieve passwords.
*
* Copyright (C) 2010 Geert Janssens <janssens.geert@telenet.be>
*
* 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 <janssens-geert@telenet.be>
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 <glib.h>
#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_ */

View File

@ -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;
}

View File

@ -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);
/* 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
{
unichar = '*';
}
dst += g_unichar_to_utf8 (unichar, dst);
if (unichar == '_')
{
dst += g_unichar_to_utf8 ('_', dst);
}
else if (unichar == ':')
{
num_colons++;
}
}
}
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;
}

View File

@ -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"

View File

@ -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. */
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");
}
#endif
}
else
{
/* If no colon found, assume it must be a file-path */
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)