From 4b398325eac49bdd16b9de216ebb636245c1fa77 Mon Sep 17 00:00:00 2001 From: Geert Janssens Date: Thu, 27 Dec 2018 20:45:43 +0100 Subject: [PATCH] Redesign gnc-uri-utils - gnc_uri_get_components will now return NULL as protocol if the input is a normal file system path instead of a uri (it used to return 'file') - gnc_uri_get_protocol will now return NULL if the input is a normal file system path instead of a uri (it used to return 'file') - gnc_uri_is_file_protocol now returns FALSE if protocol is NULL (it used to return TRUE) - gnc_uri_is_file_uri now returns FALSE if input is a normal file system path instead of a uri (it used to return TRUE) - a new function gnc_uri_targets_local_fs will return TRUE only if its input is either a file uri or a normal file system path. This function is now mostly used instead of gnc_uri_is_file_uri in the current code base - a new function gnc_uri_is_uri is added to check whether its input is a valid uri (has protocol, path and hostname for non-file uris) --- gnucash/gnome-utils/dialog-file-access.c | 2 +- gnucash/gnome-utils/gnc-file.c | 29 +++-- gnucash/gnome-utils/gnc-main-window.c | 4 +- gnucash/gnome-utils/gnc-plugin-file-history.c | 4 +- libgnucash/app-utils/gnc-state.c | 2 +- libgnucash/engine/gnc-uri-utils.c | 83 ++++++++++++-- libgnucash/engine/gnc-uri-utils.h | 102 ++++++++++++------ libgnucash/engine/test/test-gnc-uri-utils.c | 8 +- 8 files changed, 167 insertions(+), 67 deletions(-) diff --git a/gnucash/gnome-utils/dialog-file-access.c b/gnucash/gnome-utils/dialog-file-access.c index 3126d3b71e..08cb96d46f 100644 --- a/gnucash/gnome-utils/dialog-file-access.c +++ b/gnucash/gnome-utils/dialog-file-access.c @@ -335,7 +335,7 @@ gnc_ui_file_access (GtkWindow *parent, int type) if (type == FILE_ACCESS_OPEN || type == FILE_ACCESS_SAVE_AS) { last = gnc_history_get_last(); - if ( last && gnc_uri_is_file_uri ( last ) ) + if ( last && gnc_uri_targets_local_fs (last)) { gchar *filepath = gnc_uri_get_path ( last ); faw->starting_dir = g_path_get_dirname( filepath ); diff --git a/gnucash/gnome-utils/gnc-file.c b/gnucash/gnome-utils/gnc-file.c index 0f8e0e4bd3..6dc0dbd318 100644 --- a/gnucash/gnome-utils/gnc-file.c +++ b/gnucash/gnome-utils/gnc-file.c @@ -208,11 +208,11 @@ show_session_error (GtkWindow *parent, { displayname = g_strdup(_("(null)")); } - else if (! gnc_uri_is_file_uri (newfile)) /* Hide the db password in error messages */ + else if (!gnc_uri_targets_local_fs (newfile)) /* Hide the db password in error messages */ displayname = gnc_uri_normalize_uri ( newfile, FALSE); else { - /* Strip the protocol from the file name. */ + /* Strip the protocol from the file name and ensure absolute filename. */ char *uri = gnc_uri_normalize_uri(newfile, FALSE); displayname = gnc_uri_get_path(uri); g_free(uri); @@ -515,7 +515,7 @@ gnc_add_history (QofSession * session) if ( !strlen (url) ) return; - if ( gnc_uri_is_file_uri ( url ) ) + if (gnc_uri_targets_local_fs (url)) file = gnc_uri_get_path ( url ); else file = gnc_uri_normalize_uri ( url, FALSE ); /* Note that the password is not saved in history ! */ @@ -689,8 +689,10 @@ RESTART: * function will ask the user to enter a password. The user can * cancel this dialog, in which case the open file action will be * abandoned. + * Note newfile is normalized uri so we can safely call + * gnc_uri_is_file_protocol on it. */ - if ( !gnc_uri_is_file_protocol (protocol) && !password) + if (!gnc_uri_is_file_protocol (protocol) && !password) { gboolean have_valid_pw = FALSE; have_valid_pw = gnc_keyring_get_password ( NULL, protocol, hostname, port, @@ -774,10 +776,11 @@ RESTART: ); int rc; - if (! gnc_uri_is_file_uri (newfile)) /* Hide the db password in error messages */ + /* Hide the db password and local filesystem schemes in error messages */ + if (!gnc_uri_is_file_uri (newfile)) displayname = gnc_uri_normalize_uri ( newfile, FALSE); else - displayname = g_strdup (newfile); + displayname = gnc_uri_get_path (newfile); dialog = gtk_message_dialog_new(parent, 0, @@ -1050,7 +1053,7 @@ gnc_file_open (GtkWindow *parent) if (!gnc_file_query_save (parent, TRUE)) return FALSE; - if ( last && gnc_uri_is_file_uri ( last ) ) + if ( last && gnc_uri_targets_local_fs (last)) { gchar *filepath = gnc_uri_get_path ( last ); default_dir = g_path_get_dirname( filepath ); @@ -1099,7 +1102,7 @@ gnc_file_export (GtkWindow *parent) ENTER(" "); last = gnc_history_get_last(); - if ( last && gnc_uri_is_file_uri ( last ) ) + if ( last && gnc_uri_targets_local_fs (last)) { gchar *filepath = gnc_uri_get_path ( last ); default_dir = g_path_get_dirname( filepath ); @@ -1198,7 +1201,9 @@ gnc_file_do_export(GtkWindow *parent, const char * filename) newfile = norm_file; } - /* Some extra steps for file based uri's only */ + /* Some extra steps for file based uri's only + * Note newfile is normalized uri so we can safely call + * gnc_uri_is_file_protocol on it. */ if (gnc_uri_is_file_protocol(protocol)) { if (check_file_path (path)) @@ -1360,7 +1365,7 @@ gnc_file_save_as (GtkWindow *parent) ENTER(" "); last = gnc_history_get_last(); - if ( last && gnc_uri_is_file_uri ( last ) ) + if ( last && gnc_uri_targets_local_fs (last)) { gchar *filepath = gnc_uri_get_path ( last ); default_dir = g_path_get_dirname( filepath ); @@ -1430,7 +1435,9 @@ gnc_file_do_save_as (GtkWindow *parent, const char* filename) newfile = norm_file; } - /* Some extra steps for file based uri's only */ + /* Some extra steps for file based uri's only + * Note newfile is normalized uri so we can safely call + * gnc_uri_is_file_protocol on it. */ if (gnc_uri_is_file_protocol(protocol)) { if (check_file_path (path)) diff --git a/gnucash/gnome-utils/gnc-main-window.c b/gnucash/gnome-utils/gnc-main-window.c index 6e0d277c74..921e1b6803 100644 --- a/gnucash/gnome-utils/gnc-main-window.c +++ b/gnucash/gnome-utils/gnc-main-window.c @@ -1518,7 +1518,7 @@ gnc_main_window_generate_title (GncMainWindow *window) filename = g_strdup(_("Unsaved Book")); else { - if ( gnc_uri_is_file_uri ( book_id ) ) + if (gnc_uri_targets_local_fs (book_id)) { /* The filename is a true file. * The Gnome HIG 2.0 recommends only the file name (no path) be used. (p15) */ @@ -1657,7 +1657,7 @@ static gchar *generate_statusbar_lastmodified_message() return NULL; else { - if ( gnc_uri_is_file_uri ( book_id ) ) + if (gnc_uri_targets_local_fs (book_id)) { #ifdef HAVE_SYS_STAT_H /* The filename is a true file. */ diff --git a/gnucash/gnome-utils/gnc-plugin-file-history.c b/gnucash/gnome-utils/gnc-plugin-file-history.c index de911102df..fb2e064448 100644 --- a/gnucash/gnome-utils/gnc-plugin-file-history.c +++ b/gnucash/gnome-utils/gnc-plugin-file-history.c @@ -329,7 +329,7 @@ gnc_history_generate_label (int index, const gchar *filename) gchar *label, *result; gchar **splitlabel; - if ( gnc_uri_is_file_uri ( filename ) ) + if (gnc_uri_targets_local_fs (filename)) { /* for file paths, only display the file name */ gchar *filepath = gnc_uri_get_path ( filename ); @@ -368,7 +368,7 @@ static gchar * gnc_history_generate_tooltip (int index, const gchar *filename) { - if ( gnc_uri_is_file_uri ( filename ) ) + if (gnc_uri_targets_local_fs (filename)) /* for file paths, display the full file path */ return gnc_uri_get_path ( filename ); else diff --git a/libgnucash/app-utils/gnc-state.c b/libgnucash/app-utils/gnc-state.c index 8b2f8d947c..c305f082b7 100644 --- a/libgnucash/app-utils/gnc-state.c +++ b/libgnucash/app-utils/gnc-state.c @@ -97,7 +97,7 @@ gnc_state_set_base (const QofSession *session) guid = qof_entity_get_guid(QOF_INSTANCE(book)); guid_to_string_buff(guid, guid_string); - if (gnc_uri_is_file_uri (uri)) + if (gnc_uri_targets_local_fs (uri)) { /* The book_uri is a true file, use its basename. */ gchar *path = gnc_uri_get_path (uri); diff --git a/libgnucash/engine/gnc-uri-utils.c b/libgnucash/engine/gnc-uri-utils.c index 0a96978347..bfa0381a0a 100644 --- a/libgnucash/engine/gnc-uri-utils.c +++ b/libgnucash/engine/gnc-uri-utils.c @@ -27,6 +27,34 @@ #include "gnc-filepath-utils.h" #include "qofsession.h" +/* Checks if the given uri is a valid uri + */ +gboolean gnc_uri_is_uri (const gchar *uri) +{ + + gchar *protocol = NULL, *hostname = NULL; + gchar *username = NULL, *password = NULL; + gchar *path = NULL; + gint port = 0; + gboolean is_uri = FALSE; + + gnc_uri_get_components ( uri, &protocol, &hostname, &port, + &username, &password, &path ); + + /* For gnucash to consider a uri valid the following must be true: + * - protocol and path must not be NULL + * - for anything but local filesystem uris, hostname must be valid as well */ + is_uri = (protocol && path && (gnc_uri_is_file_protocol(protocol) || hostname)); + + g_free (protocol); + g_free (hostname); + g_free (username); + g_free (password); + g_free (path); + + return is_uri; +} + /* Checks if the given protocol is used to refer to a file * (as opposed to a network service) */ @@ -52,17 +80,19 @@ gboolean gnc_uri_is_known_protocol (const gchar *protocol) /* Checks if the given protocol is used to refer to a file * (as opposed to a network service) - * For simplicity, handle all unknown protocols as if it were - * file based protocols. This will avoid password lookups and such. + * Note unknown protocols are always considered network protocols. + * + * *Compatibility note:* + * This used to be the other way around before gnucash 3.4. Before + * that unknown protocols were always considered local file system + * uri protocols. */ gboolean gnc_uri_is_file_protocol (const gchar *protocol) { - if ( !g_ascii_strcasecmp (protocol, "mysql") || - !g_ascii_strcasecmp (protocol, "postgres") - ) - return FALSE; - else - return TRUE; + return (protocol && + (!g_ascii_strcasecmp (protocol, "file") || + !g_ascii_strcasecmp (protocol, "xml") || + !g_ascii_strcasecmp (protocol, "sqlite3"))); } /* Checks if the given uri defines a file @@ -78,6 +108,37 @@ gboolean gnc_uri_is_file_uri (const gchar *uri) return result; } +/* Checks if the given uri is a valid uri + */ +gboolean gnc_uri_targets_local_fs (const gchar *uri) +{ + + gchar *protocol = NULL, *hostname = NULL; + gchar *username = NULL, *password = NULL; + gchar *path = NULL; + gint port = 0; + gboolean is_local_fs = FALSE; + + gnc_uri_get_components ( uri, &protocol, &hostname, &port, + &username, &password, &path ); + + /* For gnucash to consider a uri to target the local fs: + * path must not be NULL + * AND + * protocol should be NULL + * OR + * protocol must be file type protocol (file, xml, sqlite) */ + is_local_fs = (path && (!protocol || gnc_uri_is_file_protocol(protocol))); + + g_free (protocol); + g_free (hostname); + g_free (username); + g_free (password); + g_free (path); + + return is_local_fs; +} + /* Splits a uri into its separate components */ void gnc_uri_get_components (const gchar *uri, gchar **protocol, @@ -103,9 +164,9 @@ void gnc_uri_get_components (const gchar *uri, splituri = g_strsplit ( uri, "://", 2 ); if ( splituri[1] == NULL ) { - /* No protocol means simple file uri */ - *protocol = g_strdup ( "file" ); - *path = g_strdup ( splituri[0] ); + /* No protocol means simple file path. + Set path to copy of the input. */ + *path = g_strdup ( uri ); g_strfreev ( splituri ); return; } diff --git a/libgnucash/engine/gnc-uri-utils.h b/libgnucash/engine/gnc-uri-utils.h index e2b45176b3..f1756b6080 100644 --- a/libgnucash/engine/gnc-uri-utils.h +++ b/libgnucash/engine/gnc-uri-utils.h @@ -34,6 +34,25 @@ * (being protocol, host name, port, user name, password and path) or * to compose a uri from these separate components. * + * A full uri can be described as: + * + * @li @c proto://[[username[:password]@]hostname[:port]]/path (universal uri) + * @li @c file://[localhost]/path (uri refering to a file on the local file system) + * + * Anything in square brackets is optional. + * + * While not strictly uris this function will also accept input in the form + * of a local file system path for convenience: + * + * @li @c some/filesystem/path A simple relative file system path (unix style) + * @li @c /some/filesystem/path A simple absolute file system path (unix style) + * @li @c some\\windows\\path A simple relative file system path (Windows style) + * @li @c c:\\some\\windows\\path A simple file system path (Windows style) + * + * The local path will then be considered as the path component of a uri where + * appropriate. In case of conversion from a file system path to a uri the relative + * paths will be converted to absolute paths as uris don't allow relative paths. + * */ #ifndef GNCURIUTILS_H_ @@ -42,33 +61,35 @@ #define GNC_DATAFILE_EXT ".gnucash" #define GNC_LOGFILE_EXT ".log" +/** Checks if the given uri is a valid uri + * + * A valid uri is defined by having at least a protocol and a path. + * If the uri is not referring to a file on the local file system + * a hostname should be set as well. + * + * @param uri The uri to check + * + * @return TRUE if the input is a valid uri, FALSE otherwise + */ +gboolean gnc_uri_is_uri (const gchar *uri); + /** 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. + * when 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. + * explicit protocol, NULL will be returned. * @param hostname The host name of the server to connect to. In case of - * the 'file' protocol, this will be NULL + * the local file system path, NULL will be returned * @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. + * be used. For local filesystem path this is always 0 as well. * @param username Optional user name found in this uri or NULL if none is found. * @param password Optional password found in this uri or NULL if none is found. - * @param 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. + * @param path The path found in this uri. * */ @@ -81,14 +102,6 @@ void gnc_uri_get_components (const gchar *uri, 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. @@ -96,20 +109,12 @@ void gnc_uri_get_components (const gchar *uri, * @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. + * explicit protocol, NULL will be returned. */ 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. @@ -160,8 +165,8 @@ gchar *gnc_uri_create_uri (const gchar *protocol, /** 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 file:///some/absolute/path ('file' can also be xml or sqlite) + * @li @c file://c:\\some\\windows\\path ('file' can 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 @@ -182,6 +187,19 @@ gchar *gnc_uri_create_uri (const gchar *protocol, gchar *gnc_uri_normalize_uri (const gchar *uri, gboolean allow_password); +/** Checks if the given uri is a valid uri + * + * A valid uri is defined by having at least a protocol and a path. + * If the uri is not referring to a file on the local file system + * a hostname should be set as well. + * + * @param uri The uri to check + * + * @return TRUE if the input is a valid uri, FALSE otherwise + */ +gboolean gnc_uri_is_uri (const gchar *uri); + + /** Checks if there is a backend that explicitly stated to handle the given protocol. * * @param protocol The protocol to check @@ -190,6 +208,7 @@ gchar *gnc_uri_normalize_uri (const gchar *uri, gboolean allow_password); */ gboolean gnc_uri_is_known_protocol (const gchar *protocol); + /** Checks if the given protocol is used to refer to a file * (as opposed to a network service like a database or web url) * @@ -210,6 +229,19 @@ gboolean gnc_uri_is_file_protocol (const gchar *protocol); */ gboolean gnc_uri_is_file_uri (const gchar *uri); +/** Checks if the given uri is either a valid file uri or a local filesystem path + * + * A valid uri is defined by having at least a protocol and a path. + * If the uri is not referring to a file on the local file system + * a hostname should be set as well. + * + * @param uri The uri to check + * + * @return TRUE if the input is a valid file uri or a local filesystem path. + * FALSE otherwise + */ +gboolean gnc_uri_targets_local_fs (const gchar *uri); + /** Adds an extension to the uri if: * * the uri is not empty and file based * * doesn't already have the extension diff --git a/libgnucash/engine/test/test-gnc-uri-utils.c b/libgnucash/engine/test/test-gnc-uri-utils.c index 287d942b41..9bfad16e1c 100644 --- a/libgnucash/engine/test/test-gnc-uri-utils.c +++ b/libgnucash/engine/test/test-gnc-uri-utils.c @@ -56,9 +56,9 @@ test_strings strs[] = /* basic file tests in posix like environment */ { "/test/path/file.gnucash", FALSE, - "file", NULL, NULL, NULL, "/test/path/file.gnucash", 0, + NULL, NULL, NULL, NULL, "/test/path/file.gnucash", 0, "file:///test/path/file.gnucash", - "file:///test/path/file.gnucash", TRUE + "file:///test/path/file.gnucash", FALSE }, { "file:///test/path/file.gnucash", FALSE, @@ -82,9 +82,9 @@ test_strings strs[] = /* basic file tests in windows environment */ { "c:\\test\\path\\file.gnucash", FALSE, - "file", NULL, NULL, NULL, "c:\\test\\path\\file.gnucash", 0, + NULL, NULL, NULL, NULL, "c:\\test\\path\\file.gnucash", 0, "file://c:\\test\\path\\file.gnucash", - "file://c:\\test\\path\\file.gnucash", TRUE + "file://c:\\test\\path\\file.gnucash", FALSE }, { "file://c:\\test\\path\\file.gnucash", FALSE,