/* ide-vcs-uri.c * * Copyright 2015-2019 Christian Hergert * * 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 3 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, see . * * SPDX-License-Identifier: GPL-3.0-or-later */ #define G_LOG_DOMAIN "ide-vcs-uri" #include "config.h" #include #include #include "ide-vcs-uri.h" G_DEFINE_BOXED_TYPE (IdeVcsUri, ide_vcs_uri, ide_vcs_uri_ref, ide_vcs_uri_unref) struct _IdeVcsUri { volatile gint ref_count; /* * If the URI string was created and has not been changed, we try extra * hard to provide the same URI back from ide_vcs_uri_to_string(). This * field is cleared any time any of the other fields are changed. */ gchar *non_destructive_uri; gchar *scheme; gchar *user; gchar *host; gchar *path; guint port; }; static inline void ide_vcs_uri_set_dirty (IdeVcsUri *self) { g_clear_pointer (&self->non_destructive_uri, g_free); } static gboolean ide_vcs_uri_validate (const IdeVcsUri *self) { g_assert (self != NULL); if (g_strcmp0 (self->scheme, "file") == 0) return ((self->path != NULL) && (self->port == 0) && (self->host == NULL) && (self->user == NULL)); if ((g_strcmp0 (self->scheme, "http") == 0) || (g_strcmp0 (self->scheme, "ssh") == 0) || (g_strcmp0 (self->scheme, "git") == 0) || (g_strcmp0 (self->scheme, "https") == 0) || (g_strcmp0 (self->scheme, "rsync") == 0)) return ((self->path != NULL) && (self->host != NULL)); return TRUE; } static gboolean ide_vcs_uri_parse (IdeVcsUri *self, const gchar *str) { static GRegex *regex1; static GRegex *regex2; static GRegex *regex3; static gsize initialized; GMatchInfo *match_info = NULL; gboolean ret = FALSE; if (g_once_init_enter (&initialized)) { /* http://stackoverflow.com/questions/2514859/regular-expression-for-git-repository */ regex1 = g_regex_new ("file://(.*)", 0, 0, NULL); g_assert (regex1); regex2 = g_regex_new ("(\\w+://)(.+@)*([\\w\\d\\.]+)(:[\\d]+){0,1}/*(.*)", 0, 0, NULL); g_assert (regex2); regex3 = g_regex_new ("(.+@)*([\\w\\d\\.]+):(.*)", 0, 0, NULL); g_assert (regex3); g_once_init_leave (&initialized, TRUE); } if (str == NULL) return FALSE; /* check for local file:// style uris */ g_regex_match (regex1, str, 0, &match_info); if (g_match_info_matches (match_info)) { g_autofree gchar *path = NULL; path = g_match_info_fetch (match_info, 1); ide_vcs_uri_set_scheme (self, "file://"); ide_vcs_uri_set_user (self, NULL); ide_vcs_uri_set_host (self, NULL); ide_vcs_uri_set_port (self, 0); ide_vcs_uri_set_path (self, path); ret = TRUE; } g_clear_pointer (&match_info, g_match_info_free); if (ret) return ret; /* check for ssh:// style network uris */ g_regex_match (regex2, str, 0, &match_info); if (g_match_info_matches (match_info)) { g_autofree gchar *scheme = NULL; g_autofree gchar *user = NULL; g_autofree gchar *host = NULL; g_autofree gchar *path = NULL; g_autofree gchar *portstr = NULL; gint start_pos; gint end_pos; guint port = 0; scheme = g_match_info_fetch (match_info, 1); user = g_match_info_fetch (match_info, 2); host = g_match_info_fetch (match_info, 3); portstr = g_match_info_fetch (match_info, 4); path = g_match_info_fetch (match_info, 5); g_match_info_fetch_pos (match_info, 5, &start_pos, &end_pos); if (*path != '~' && (start_pos > 0) && str [start_pos-1] == '/') { gchar *tmp; tmp = path; path = g_strdup_printf ("/%s", path); g_free (tmp); } if (!ide_str_empty0 (portstr) && g_ascii_isdigit (portstr [1])) port = CLAMP (atoi (&portstr [1]), 1, G_MAXINT16); ide_vcs_uri_set_scheme (self, scheme); ide_vcs_uri_set_user (self, user); ide_vcs_uri_set_host (self, host); ide_vcs_uri_set_port (self, port); ide_vcs_uri_set_path (self, path); ret = TRUE; } g_clear_pointer (&match_info, g_match_info_free); if (ret) return ret; /* check for user@host style uris */ g_regex_match (regex3, str, 0, &match_info); if (g_match_info_matches (match_info)) { g_autofree gchar *user = NULL; g_autofree gchar *host = NULL; g_autofree gchar *path = NULL; user = g_match_info_fetch (match_info, 1); host = g_match_info_fetch (match_info, 2); path = g_match_info_fetch (match_info, 3); if (path && path[0] != '~' && path[0] != '/') { g_autofree gchar *tmp = path; path = g_strdup_printf ("~/%s", tmp); } ide_vcs_uri_set_user (self, user); ide_vcs_uri_set_host (self, host); ide_vcs_uri_set_path (self, path); ide_vcs_uri_set_scheme (self, "ssh://"); ret = TRUE; } g_clear_pointer (&match_info, g_match_info_free); if (ret) return ret; /* try to avoid some in-progress schemes */ if (strstr (str, "://")) return FALSE; ide_vcs_uri_set_scheme (self, "file://"); ide_vcs_uri_set_user (self, NULL); ide_vcs_uri_set_host (self, NULL); ide_vcs_uri_set_port (self, 0); ide_vcs_uri_set_path (self, str); return TRUE; } IdeVcsUri * ide_vcs_uri_new (const gchar *uri) { IdeVcsUri *self; self = g_slice_new0 (IdeVcsUri); self->ref_count = 1; if (ide_vcs_uri_parse (self, uri) && ide_vcs_uri_validate (self)) { self->non_destructive_uri = g_strdup (uri); return self; } ide_vcs_uri_unref (self); return NULL; } static void ide_vcs_uri_finalize (IdeVcsUri *self) { g_free (self->non_destructive_uri); g_free (self->scheme); g_free (self->user); g_free (self->host); g_free (self->path); g_slice_free (IdeVcsUri, self); } IdeVcsUri * ide_vcs_uri_ref (IdeVcsUri *self) { g_return_val_if_fail (self, NULL); g_return_val_if_fail (self->ref_count > 0, NULL); g_atomic_int_inc (&self->ref_count); return self; } void ide_vcs_uri_unref (IdeVcsUri *self) { g_return_if_fail (self); g_return_if_fail (self->ref_count > 0); if (g_atomic_int_dec_and_test (&self->ref_count)) ide_vcs_uri_finalize (self); } const gchar * ide_vcs_uri_get_scheme (const IdeVcsUri *self) { g_return_val_if_fail (self, NULL); return self->scheme; } const gchar * ide_vcs_uri_get_user (const IdeVcsUri *self) { g_return_val_if_fail (self, NULL); return self->user; } const gchar * ide_vcs_uri_get_host (const IdeVcsUri *self) { g_return_val_if_fail (self, NULL); return self->host; } guint ide_vcs_uri_get_port (const IdeVcsUri *self) { g_return_val_if_fail (self, 0); return self->port; } const gchar * ide_vcs_uri_get_path (const IdeVcsUri *self) { g_return_val_if_fail (self, NULL); return self->path; } void ide_vcs_uri_set_scheme (IdeVcsUri *self, const gchar *scheme) { g_return_if_fail (self); if (ide_str_empty0 (scheme)) scheme = NULL; if (scheme != self->scheme) { const gchar *tmp; g_clear_pointer (&self->scheme, g_free); if (scheme != NULL && (tmp = strchr (scheme, ':'))) self->scheme = g_strndup (scheme, tmp - scheme); else self->scheme = g_strdup (scheme); } ide_vcs_uri_set_dirty (self); } void ide_vcs_uri_set_user (IdeVcsUri *self, const gchar *user) { g_return_if_fail (self); if (ide_str_empty0 (user)) user = NULL; if (user != self->user) { const gchar *tmp; g_clear_pointer (&self->user, g_free); if (user != NULL && (tmp = strchr (user, '@'))) self->user = g_strndup (user, tmp - user); else self->user = g_strdup (user); } ide_vcs_uri_set_dirty (self); } void ide_vcs_uri_set_host (IdeVcsUri *self, const gchar *host) { g_return_if_fail (self); if (ide_str_empty0 (host)) host = NULL; if (host != self->host) { g_free (self->host); self->host = g_strdup (host); } ide_vcs_uri_set_dirty (self); } void ide_vcs_uri_set_port (IdeVcsUri *self, guint port) { g_return_if_fail (self); g_return_if_fail (port <= G_MAXINT16); self->port = port; ide_vcs_uri_set_dirty (self); } void ide_vcs_uri_set_path (IdeVcsUri *self, const gchar *path) { g_return_if_fail (self); if (ide_str_empty0 (path)) path = NULL; if (path != self->path) { if (path != NULL && (*path == ':')) path++; g_free (self->path); self->path = g_strdup (path); } ide_vcs_uri_set_dirty (self); } gchar * ide_vcs_uri_to_string (const IdeVcsUri *self) { GString *str; g_return_val_if_fail (self, NULL); if (self->non_destructive_uri != NULL) return g_strdup (self->non_destructive_uri); str = g_string_new (NULL); g_string_append_printf (str, "%s://", self->scheme); if (0 == g_strcmp0 (self->scheme, "file")) { g_string_append (str, self->path); return g_string_free (str, FALSE); } if (self->user != NULL) g_string_append_printf (str, "%s@", self->user); g_string_append (str, self->host); if (self->port != 0) g_string_append_printf (str, ":%u", self->port); if (self->path == NULL) g_string_append (str, "/"); else if (self->path [0] == '~') g_string_append_printf (str, "/%s", self->path); else if (self->path [0] != '/') g_string_append_printf (str, "/%s", self->path); else g_string_append (str, self->path); return g_string_free (str, FALSE); } gboolean ide_vcs_uri_is_valid (const gchar *uri_string) { gboolean ret = FALSE; if (uri_string != NULL) { IdeVcsUri *uri; uri = ide_vcs_uri_new (uri_string); ret = !!uri; g_clear_pointer (&uri, ide_vcs_uri_unref); } return ret; } /** * ide_vcs_uri_get_clone_name: * @self: an #ideVcsUri * * Determines a suggested name for the checkout directory. Some special * handling of suffixes such as ".git" are performed to improve the the * quality of results. * * Returns: (transfer full) (nullable): a string containing the suggested * clone directory name, or %NULL. * * Since: 3.32 */ gchar * ide_vcs_uri_get_clone_name (const IdeVcsUri *self) { g_autofree gchar *name = NULL; const gchar *path; g_return_val_if_fail (self != NULL, NULL); if (!(path = ide_vcs_uri_get_path (self))) return NULL; if (ide_str_empty0 (path)) return NULL; if (!(name = g_path_get_basename (path))) return NULL; /* Trim trailing ".git" */ if (g_str_has_suffix (name, ".git")) *(strrchr (name, '.')) = '\0'; if (!g_str_equal (name, "/") && !g_str_equal (name, "~")) return g_steal_pointer (&name); return NULL; }