1 /*
2  * pluma-utils.c
3  * This file is part of pluma
4  *
5  * Copyright (C) 1998, 1999 Alex Roberts, Evan Lawrence
6  * Copyright (C) 2000, 2002 Chema Celorio, Paolo Maggi
7  * Copyright (C) 2003-2005 Paolo Maggi
8  * Copyright (C) 2012-2021 MATE Developers
9  *
10  * This program is free software; you can redistribute it and/or modify
11  * it under the terms of the GNU General Public License as published by
12  * the Free Software Foundation; either version 2 of the License, or
13  * (at your option) any later version.
14  *
15  * This program is distributed in the hope that it will be useful,
16  * but WITHOUT ANY WARRANTY; without even the implied warranty of
17  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18  * GNU General Public License for more details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program; if not, write to the Free Software
22  * Foundation, Inc., 51 Franklin St, Fifth Floor,
23  * Boston, MA 02110-1301, USA.
24  */
25 
26 /*
27  * Modified by the pluma Team, 1998-2005. See the AUTHORS file for a
28  * list of people on the pluma Team.
29  * See the ChangeLog files for a list of changes.
30  *
31  * $Id$
32  */
33 
34 #ifdef HAVE_CONFIG_H
35 #include <config.h>
36 #endif
37 
38 #include <errno.h>
39 #include <sys/types.h>
40 #include <sys/stat.h>
41 #include <sys/time.h>
42 #include <fcntl.h>
43 #include <string.h>
44 
45 #include <glib.h>
46 #include <glib/gi18n.h>
47 #include <gio/gio.h>
48 
49 #include "pluma-utils.h"
50 
51 #include "pluma-settings.h"
52 #include "pluma-document.h"
53 #include "pluma-debug.h"
54 
55 /* For the workspace/viewport stuff */
56 #include <gdk/gdkx.h>
57 #include <X11/Xlib.h>
58 #include <X11/Xutil.h>
59 #include <X11/Xatom.h>
60 
61 /**
62  * pluma_utils_uris_has_file_scheme
63  *
64  * Returns: %TRUE if @uri is a file: uri and is not a chained uri
65  */
66 gboolean
pluma_utils_uri_has_file_scheme(const gchar * uri)67 pluma_utils_uri_has_file_scheme (const gchar *uri)
68 {
69 	GFile *gfile;
70 	gboolean res;
71 
72 	gfile = g_file_new_for_uri (uri);
73 	res = g_file_has_uri_scheme (gfile, "file");
74 
75 	g_object_unref (gfile);
76 	return res;
77 }
78 
79 /* FIXME: we should check for chained URIs */
80 gboolean
pluma_utils_uri_has_writable_scheme(const gchar * uri)81 pluma_utils_uri_has_writable_scheme (const gchar *uri)
82 {
83 	/* FIXME: Avoid making a new settings variable here. */
84 	GSettings *settings;
85 	GFile *gfile;
86 	gchar *scheme;
87 	GSList *writable_schemes;
88 	gboolean res;
89 
90 	settings = g_settings_new (PLUMA_SCHEMA_ID);
91 	gfile = g_file_new_for_uri (uri);
92 	scheme = g_file_get_uri_scheme (gfile);
93 
94 	g_return_val_if_fail (scheme != NULL, FALSE);
95 
96 	g_object_unref (gfile);
97 
98 	writable_schemes = pluma_settings_get_writable_vfs_schemes (settings);
99 
100 	/* CHECK: should we use g_ascii_strcasecmp? - Paolo (Nov 6, 2005) */
101 	res = (g_slist_find_custom (writable_schemes,
102 				    scheme,
103 				    (GCompareFunc)strcmp) != NULL);
104 
105 	g_slist_free_full (writable_schemes, g_free);
106 
107 	g_free (scheme);
108 	g_object_unref (settings);
109 
110 	return res;
111 }
112 
113 static void
widget_get_origin(GtkWidget * widget,gint * x,gint * y)114 widget_get_origin (GtkWidget *widget, gint *x, gint *y)
115 
116 {
117 	GdkWindow *window;
118 
119 	window = gtk_widget_get_window (widget);
120 	gdk_window_get_origin (window, x, y);
121 }
122 
123 void
pluma_utils_menu_position_under_widget(GtkMenu * menu,gint * x,gint * y,gboolean * push_in,gpointer user_data)124 pluma_utils_menu_position_under_widget (GtkMenu  *menu,
125 					gint     *x,
126 					gint     *y,
127 					gboolean *push_in,
128 					gpointer  user_data)
129 {
130 	GtkWidget *widget;
131 	GtkRequisition requisition;
132 	GtkAllocation allocation;
133 
134 	widget = GTK_WIDGET (user_data);
135 	widget_get_origin (widget, x, y);
136 
137 	gtk_widget_get_preferred_size (GTK_WIDGET (menu), NULL, &requisition);
138 
139 	gtk_widget_get_allocation (widget, &allocation);
140 
141 	if (gtk_widget_get_direction (widget) == GTK_TEXT_DIR_RTL)
142 	{
143 		*x += allocation.x + allocation.width - requisition.width;
144 	}
145 	else
146 	{
147 		*x += allocation.x;
148 	}
149 
150 	*y += allocation.y + allocation.height;
151 
152 	*push_in = TRUE;
153 }
154 
155 void
menu_popup_at_treeview_selection(GtkWidget * menu,GtkWidget * treeview)156 menu_popup_at_treeview_selection (GtkWidget *menu,
157 				  GtkWidget *treeview)
158 {
159 	GtkTreePath *path;
160 	GtkTreeViewColumn *column;
161 	GdkWindow *bin_window;
162 	GdkRectangle rect;
163 
164 	gtk_tree_view_get_cursor (GTK_TREE_VIEW (treeview), &path, &column);
165 	g_return_if_fail (path != NULL);
166 
167 	if (column == NULL)
168 		column = gtk_tree_view_get_column (GTK_TREE_VIEW (treeview), 0);
169 
170 	bin_window = gtk_tree_view_get_bin_window (GTK_TREE_VIEW (treeview));
171 	gtk_tree_view_get_cell_area (GTK_TREE_VIEW (treeview), path, column, &rect);
172 
173 	gtk_menu_popup_at_rect (GTK_MENU (menu), bin_window, &rect,
174 				GDK_GRAVITY_SOUTH_WEST, GDK_GRAVITY_NORTH_WEST,
175 				NULL);
176 
177 	gtk_tree_path_free(path);
178 }
179 
180 /**
181  * pluma_gtk_button_new_with_icon:
182  * @label:
183  * @icon_name:
184  *
185  * Returns: (transfer full):
186  */
187 
188 GtkWidget *
pluma_gtk_button_new_with_icon(const gchar * label,const gchar * icon_name)189 pluma_gtk_button_new_with_icon (const gchar *label,
190 				const gchar *icon_name)
191 {
192 	GtkWidget *button;
193 
194 	button = gtk_button_new_with_mnemonic (label);
195 	gtk_button_set_image (GTK_BUTTON (button),
196 			      gtk_image_new_from_icon_name (icon_name,
197 							    GTK_ICON_SIZE_BUTTON));
198 
199         return button;
200 }
201 
202 /**
203  * pluma_dialog_add_button:
204  * @dialog:
205  * @text:
206  * @icon_name:
207  * @response_id:
208  *
209  * Returns: (transfer none):
210  */
211 GtkWidget *
pluma_dialog_add_button(GtkDialog * dialog,const gchar * text,const gchar * icon_name,gint response_id)212 pluma_dialog_add_button (GtkDialog   *dialog,
213 			 const gchar *text,
214 			 const gchar *icon_name,
215 			 gint         response_id)
216 {
217 	GtkWidget *button;
218 
219 	g_return_val_if_fail (GTK_IS_DIALOG (dialog), NULL);
220 	g_return_val_if_fail (text != NULL, NULL);
221 	g_return_val_if_fail (icon_name != NULL, NULL);
222 
223 	button = pluma_gtk_button_new_with_icon (text, icon_name);
224 	g_return_val_if_fail (button != NULL, NULL);
225 
226 	gtk_widget_set_can_default (button, TRUE);
227 
228 	gtk_widget_show (button);
229 
230 	gtk_dialog_add_action_widget (dialog, button, response_id);
231 
232 	return button;
233 }
234 
235 /*
236  * n: len of the string in bytes
237  */
238 gboolean
g_utf8_caselessnmatch(const char * s1,const char * s2,gssize n1,gssize n2)239 g_utf8_caselessnmatch (const char *s1, const char *s2, gssize n1, gssize n2)
240 {
241 	gchar *casefold;
242 	gchar *normalized_s1;
243       	gchar *normalized_s2;
244 	gint len_s1;
245 	gint len_s2;
246 	gboolean ret = FALSE;
247 
248 	g_return_val_if_fail (s1 != NULL, FALSE);
249 	g_return_val_if_fail (s2 != NULL, FALSE);
250 	g_return_val_if_fail (n1 > 0, FALSE);
251 	g_return_val_if_fail (n2 > 0, FALSE);
252 
253 	casefold = g_utf8_casefold (s1, n1);
254 	normalized_s1 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
255 	g_free (casefold);
256 
257 	casefold = g_utf8_casefold (s2, n2);
258 	normalized_s2 = g_utf8_normalize (casefold, -1, G_NORMALIZE_NFD);
259 	g_free (casefold);
260 
261 	len_s1 = strlen (normalized_s1);
262 	len_s2 = strlen (normalized_s2);
263 
264 	if (len_s1 < len_s2)
265 		goto finally_2;
266 
267 	ret = (strncmp (normalized_s1, normalized_s2, len_s2) == 0);
268 
269 finally_2:
270 	g_free (normalized_s1);
271 	g_free (normalized_s2);
272 
273 	return ret;
274 }
275 
276 /**
277  * pluma_utils_set_atk_name_description:
278  * @widget: The Gtk widget for which name/description to be set
279  * @name: Atk name string
280  * @description: Atk description string
281  *
282  * This function sets up name and description
283  * for a specified gtk widget.
284  */
285 void
pluma_utils_set_atk_name_description(GtkWidget * widget,const gchar * name,const gchar * description)286 pluma_utils_set_atk_name_description (GtkWidget *widget,
287 				      const gchar *name,
288 				      const gchar *description)
289 {
290 	AtkObject *aobj;
291 
292 	aobj = gtk_widget_get_accessible (widget);
293 
294 	if (!(GTK_IS_ACCESSIBLE (aobj)))
295 		return;
296 
297 	if(name)
298 		atk_object_set_name (aobj, name);
299 
300 	if(description)
301 		atk_object_set_description (aobj, description);
302 }
303 
304 /**
305  * pluma_set_atk_relation:
306  * @obj1: specified widget.
307  * @obj2: specified widget.
308  * @rel_type: the type of relation to set up.
309  *
310  * This function establishes atk relation
311  * between 2 specified widgets.
312  */
313 void
pluma_utils_set_atk_relation(GtkWidget * obj1,GtkWidget * obj2,AtkRelationType rel_type)314 pluma_utils_set_atk_relation (GtkWidget *obj1,
315 			      GtkWidget *obj2,
316 			      AtkRelationType rel_type )
317 {
318 	AtkObject *atk_obj1, *atk_obj2;
319 	AtkRelationSet *relation_set;
320 	AtkObject *targets[1];
321 	AtkRelation *relation;
322 
323 	atk_obj1 = gtk_widget_get_accessible (obj1);
324 	atk_obj2 = gtk_widget_get_accessible (obj2);
325 
326 	if (!(GTK_IS_ACCESSIBLE (atk_obj1)) || !(GTK_IS_ACCESSIBLE (atk_obj2)))
327 		return;
328 
329 	relation_set = atk_object_ref_relation_set (atk_obj1);
330 	targets[0] = atk_obj2;
331 
332 	relation = atk_relation_new (targets, 1, rel_type);
333 	atk_relation_set_add (relation_set, relation);
334 
335 	g_object_unref (G_OBJECT (relation));
336 }
337 
338 gboolean
pluma_utils_uri_exists(const gchar * text_uri)339 pluma_utils_uri_exists (const gchar* text_uri)
340 {
341 	GFile *gfile;
342 	gboolean res;
343 
344 	g_return_val_if_fail (text_uri != NULL, FALSE);
345 
346 	pluma_debug_message (DEBUG_UTILS, "text_uri: %s", text_uri);
347 
348 	gfile = g_file_new_for_uri (text_uri);
349 	res = g_file_query_exists (gfile, NULL);
350 
351 	g_object_unref (gfile);
352 
353 	pluma_debug_message (DEBUG_UTILS, res ? "TRUE" : "FALSE");
354 
355 	return res;
356 }
357 
358 gchar *
pluma_utils_escape_search_text(const gchar * text)359 pluma_utils_escape_search_text (const gchar* text)
360 {
361 	GString *str;
362 	gint length;
363 	const gchar *p;
364  	const gchar *end;
365 
366 	if (text == NULL)
367 		return NULL;
368 
369 	pluma_debug_message (DEBUG_SEARCH, "Text: %s", text);
370 
371     	length = strlen (text);
372 
373 	/* no escape when typing.
374 	 * The short circuit works only for ascii, but we only
375 	 * care about not escaping a single '\' */
376 	if (length == 1)
377 		return g_strdup (text);
378 
379 	str = g_string_new ("");
380 
381 	p = text;
382   	end = text + length;
383 
384   	while (p != end)
385     	{
386       		const gchar *next;
387       		next = g_utf8_next_char (p);
388 
389 		switch (*p)
390         	{
391        			case '\n':
392           			g_string_append (str, "\\n");
393           			break;
394 			case '\r':
395           			g_string_append (str, "\\r");
396           			break;
397 			case '\t':
398           			g_string_append (str, "\\t");
399           			break;
400 			case '\\':
401           			g_string_append (str, "\\\\");
402           			break;
403         		default:
404           			g_string_append_len (str, p, next - p);
405           			break;
406         	}
407 
408       		p = next;
409     	}
410 
411 	return g_string_free (str, FALSE);
412 }
413 
414 gchar *
pluma_utils_unescape_search_text(const gchar * text)415 pluma_utils_unescape_search_text (const gchar *text)
416 {
417 	GString *str;
418 	gint length;
419 	gboolean drop_prev = FALSE;
420 	const gchar *cur;
421 	const gchar *end;
422 	const gchar *prev;
423 
424 	if (text == NULL)
425 		return NULL;
426 
427 	length = strlen (text);
428 
429 	str = g_string_new ("");
430 
431 	cur = text;
432 	end = text + length;
433 	prev = NULL;
434 
435 	while (cur != end)
436 	{
437 		const gchar *next;
438 		next = g_utf8_next_char (cur);
439 
440 		if (prev && (*prev == '\\'))
441 		{
442 			switch (*cur)
443 			{
444 				case 'n':
445 					str = g_string_append (str, "\n");
446 				break;
447 				case 'r':
448 					str = g_string_append (str, "\r");
449 				break;
450 				case 't':
451 					str = g_string_append (str, "\t");
452 				break;
453 				case '\\':
454 					str = g_string_append (str, "\\");
455 					drop_prev = TRUE;
456 				break;
457 				default:
458 					str = g_string_append (str, "\\");
459 					str = g_string_append_len (str, cur, next - cur);
460 				break;
461 			}
462 		}
463 		else if (*cur != '\\')
464 		{
465 			str = g_string_append_len (str, cur, next - cur);
466 		}
467 		else if ((next == end) && (*cur == '\\'))
468 		{
469 			str = g_string_append (str, "\\");
470 		}
471 
472 		if (!drop_prev)
473 		{
474 			prev = cur;
475 		}
476 		else
477 		{
478 			prev = NULL;
479 			drop_prev = FALSE;
480 		}
481 
482 		cur = next;
483 	}
484 
485 	return g_string_free (str, FALSE);
486 }
487 
488 void
pluma_warning(GtkWindow * parent,const gchar * format,...)489 pluma_warning (GtkWindow *parent, const gchar *format, ...)
490 {
491 	va_list         args;
492 	gchar          *str;
493 	GtkWidget      *dialog;
494 	GtkWindowGroup *wg = NULL;
495 
496 	g_return_if_fail (format != NULL);
497 
498 	if (parent != NULL)
499 		wg = gtk_window_get_group (parent);
500 
501 	va_start (args, format);
502 	str = g_strdup_vprintf (format, args);
503 	va_end (args);
504 
505 	dialog = gtk_message_dialog_new_with_markup (
506 			parent,
507 			GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
508 		   	GTK_MESSAGE_ERROR,
509 		   	GTK_BUTTONS_OK,
510 			"%s", str);
511 
512 	g_free (str);
513 
514 	if (wg != NULL)
515 		gtk_window_group_add_window (wg, GTK_WINDOW (dialog));
516 
517 	gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_OK);
518 
519 	gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE);
520 
521 	g_signal_connect (G_OBJECT (dialog),
522 			  "response",
523 			  G_CALLBACK (gtk_widget_destroy),
524 			  NULL);
525 
526 	gtk_widget_show (dialog);
527 }
528 
529 /*
530  * Doubles underscore to avoid spurious menu accels.
531  */
532 gchar *
pluma_utils_escape_underscores(const gchar * text,gssize length)533 pluma_utils_escape_underscores (const gchar* text,
534 				gssize       length)
535 {
536 	GString *str;
537 	const gchar *p;
538 	const gchar *end;
539 
540 	g_return_val_if_fail (text != NULL, NULL);
541 
542 	if (length < 0)
543 		length = strlen (text);
544 
545 	str = g_string_sized_new (length);
546 
547 	p = text;
548 	end = text + length;
549 
550 	while (p != end)
551 	{
552 		const gchar *next;
553 		next = g_utf8_next_char (p);
554 
555 		switch (*p)
556 		{
557 			case '_':
558 				g_string_append (str, "__");
559 				break;
560 			default:
561 				g_string_append_len (str, p, next - p);
562 				break;
563 		}
564 
565 		p = next;
566 	}
567 
568 	return g_string_free (str, FALSE);
569 }
570 
571 /* the following functions are taken from eel */
572 
573 static gchar *
pluma_utils_str_truncate(const gchar * string,guint truncate_length,gboolean middle)574 pluma_utils_str_truncate (const gchar *string,
575 			  guint        truncate_length,
576 			  gboolean middle)
577 {
578 	GString     *truncated;
579 	guint        length;
580 	guint        n_chars;
581 	guint        num_left_chars;
582 	guint        right_offset;
583 	guint        delimiter_length;
584 	const gchar *delimiter = "\342\200\246";
585 
586 	g_return_val_if_fail (string != NULL, NULL);
587 
588 	length = strlen (string);
589 
590 	g_return_val_if_fail (g_utf8_validate (string, length, NULL), NULL);
591 
592 	/* It doesnt make sense to truncate strings to less than
593 	 * the size of the delimiter plus 2 characters (one on each
594 	 * side)
595 	 */
596 	delimiter_length = g_utf8_strlen (delimiter, -1);
597 	if (truncate_length < (delimiter_length + 2)) {
598 		return g_strdup (string);
599 	}
600 
601 	n_chars = g_utf8_strlen (string, length);
602 
603 	/* Make sure the string is not already small enough. */
604 	if (n_chars <= truncate_length) {
605 		return g_strdup (string);
606 	}
607 
608 	/* Find the 'middle' where the truncation will occur. */
609 	if (middle)
610 	{
611 		num_left_chars = (truncate_length - delimiter_length) / 2;
612 		right_offset = n_chars - truncate_length + num_left_chars + delimiter_length;
613 
614 		truncated = g_string_new_len (string,
615 					      g_utf8_offset_to_pointer (string, num_left_chars) - string);
616 		g_string_append (truncated, delimiter);
617 		g_string_append (truncated, g_utf8_offset_to_pointer (string, right_offset));
618 	}
619 	else
620 	{
621 		num_left_chars = truncate_length - delimiter_length;
622 		truncated = g_string_new_len (string,
623 					      g_utf8_offset_to_pointer (string, num_left_chars) - string);
624 		g_string_append (truncated, delimiter);
625 	}
626 
627 	return g_string_free (truncated, FALSE);
628 }
629 
630 gchar *
pluma_utils_str_middle_truncate(const gchar * string,guint truncate_length)631 pluma_utils_str_middle_truncate (const gchar *string,
632 				 guint        truncate_length)
633 {
634 	return pluma_utils_str_truncate (string, truncate_length, TRUE);
635 }
636 
637 gchar *
pluma_utils_str_end_truncate(const gchar * string,guint truncate_length)638 pluma_utils_str_end_truncate (const gchar *string,
639 			      guint        truncate_length)
640 {
641 	return pluma_utils_str_truncate (string, truncate_length, FALSE);
642 }
643 
644 gchar *
pluma_utils_make_valid_utf8(const char * name)645 pluma_utils_make_valid_utf8 (const char *name)
646 {
647 	GString *string;
648 	const char *remainder, *invalid;
649 	int remaining_bytes, valid_bytes;
650 
651 	g_return_val_if_fail (name != NULL, NULL);
652 
653 	string = NULL;
654 	remainder = name;
655 	remaining_bytes = strlen (name);
656 
657 	while (remaining_bytes != 0) {
658 		if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
659 			break;
660 		}
661 		valid_bytes = invalid - remainder;
662 
663 		if (string == NULL) {
664 			string = g_string_sized_new (remaining_bytes);
665 		}
666 		g_string_append_len (string, remainder, valid_bytes);
667 		/* append U+FFFD REPLACEMENT CHARACTER */
668 		g_string_append (string, "\357\277\275");
669 
670 		remaining_bytes -= valid_bytes + 1;
671 		remainder = invalid + 1;
672 	}
673 
674 	if (string == NULL) {
675 		return g_strdup (name);
676 	}
677 
678 	g_string_append (string, remainder);
679 
680 	g_assert (g_utf8_validate (string->str, -1, NULL));
681 
682 	return g_string_free (string, FALSE);
683 }
684 
685 /**
686  * pluma_utils_uri_get_dirname:
687  *
688  * Note: this function replace home dir with ~
689  */
690 gchar *
pluma_utils_uri_get_dirname(const gchar * uri)691 pluma_utils_uri_get_dirname (const gchar *uri)
692 {
693 	gchar *res;
694 	gchar *str;
695 
696 	g_return_val_if_fail (uri != NULL, NULL);
697 
698 	/* CHECK: does it work with uri chaining? - Paolo */
699 	str = g_path_get_dirname (uri);
700 	g_return_val_if_fail (str != NULL, g_strdup ("."));
701 
702 	if ((strlen (str) == 1) && (*str == '.'))
703 	{
704 		g_free (str);
705 
706 		return NULL;
707 	}
708 
709 	res = pluma_utils_replace_home_dir_with_tilde (str);
710 
711 	g_free (str);
712 
713 	return res;
714 }
715 
716 /**
717  * pluma_utils_location_get_dirname_for_display:
718  * @location: the location
719  *
720  * Returns a string suitable to be displayed in the UI indicating
721  * the name of the directory where the file is located.
722  * For remote files it may also contain the hostname etc.
723  * For local files it tries to replace the home dir with ~.
724  *
725  * Returns: a string to display the dirname
726  */
727 gchar *
pluma_utils_location_get_dirname_for_display(GFile * location)728 pluma_utils_location_get_dirname_for_display (GFile *location)
729 {
730 	gchar *uri;
731 	gchar *res;
732 	GMount *mount;
733 
734 	g_return_val_if_fail (location != NULL, NULL);
735 
736 	/* we use the parse name, that is either the local path
737 	 * or an uri but which is utf8 safe */
738 	uri = g_file_get_parse_name (location);
739 
740 	/* FIXME: this is sync... is it a problem? */
741 	mount = g_file_find_enclosing_mount (location, NULL, NULL);
742 	if (mount != NULL)
743 	{
744 		gchar *mount_name;
745 		gchar *path = NULL;
746 		gchar *dirname;
747 
748 		mount_name = g_mount_get_name (mount);
749 		g_object_unref (mount);
750 
751 		/* obtain the "path" part of the uri */
752 		pluma_utils_decode_uri (uri,
753 					NULL, NULL,
754 					NULL, NULL,
755 					&path);
756 
757 		if (path == NULL)
758 		{
759 			dirname = pluma_utils_uri_get_dirname (uri);
760 		}
761 		else
762 		{
763 			dirname = pluma_utils_uri_get_dirname (path);
764 		}
765 
766 		if (dirname == NULL || strcmp (dirname, ".") == 0)
767 		{
768 			res = mount_name;
769 		}
770 		else
771 		{
772 			res = g_strdup_printf ("%s %s", mount_name, dirname);
773 			g_free (mount_name);
774 		}
775 
776 		g_free (path);
777 		g_free (dirname);
778 	}
779 	else
780 	{
781 		/* fallback for local files or uris without mounts */
782 		res = pluma_utils_uri_get_dirname (uri);
783 	}
784 
785 	g_free (uri);
786 
787 	return res;
788 }
789 
790 gchar *
pluma_utils_replace_home_dir_with_tilde(const gchar * uri)791 pluma_utils_replace_home_dir_with_tilde (const gchar *uri)
792 {
793 	gchar *tmp;
794 	gchar *home;
795 
796 	g_return_val_if_fail (uri != NULL, NULL);
797 
798 	/* Note that g_get_home_dir returns a const string */
799 	tmp = (gchar *)g_get_home_dir ();
800 
801 	if (tmp == NULL)
802 		return g_strdup (uri);
803 
804 	home = g_filename_to_utf8 (tmp, -1, NULL, NULL, NULL);
805 	if (home == NULL)
806 		return g_strdup (uri);
807 
808 	if (strcmp (uri, home) == 0)
809 	{
810 		g_free (home);
811 
812 		return g_strdup ("~");
813 	}
814 
815 	tmp = home;
816 	home = g_strdup_printf ("%s/", tmp);
817 	g_free (tmp);
818 
819 	if (g_str_has_prefix (uri, home))
820 	{
821 		gchar *res;
822 
823 		res = g_strdup_printf ("~/%s", uri + strlen (home));
824 
825 		g_free (home);
826 
827 		return res;
828 	}
829 
830 	g_free (home);
831 
832 	return g_strdup (uri);
833 }
834 
835 /* the following two functions are courtesy of galeon */
836 
837 /**
838  * pluma_utils_get_current_workspace:
839  *
840  * Get the current workspace
841  *
842  * Get the currently visible workspace for the #GdkScreen.
843  *
844  * If the X11 window property isn't found, 0 (the first workspace)
845  * is returned.
846  */
847 guint
pluma_utils_get_current_workspace(GdkScreen * screen)848 pluma_utils_get_current_workspace (GdkScreen *screen)
849 {
850 	if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
851 	{
852 		GdkWindow *root_win;
853 		GdkDisplay *display;
854 		Atom type;
855 		gint format;
856 		gulong nitems;
857 		gulong bytes_after;
858 		guint *current_desktop;
859 		gint err, result;
860 		guint ret = 0;
861 
862 		g_return_val_if_fail (GDK_IS_SCREEN (screen), 0);
863 
864 		root_win = gdk_screen_get_root_window (screen);
865 		display = gdk_screen_get_display (screen);
866 
867 		gdk_x11_display_error_trap_push (display);
868 		result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID(root_win),
869 					     gdk_x11_get_xatom_by_name_for_display (display, "_NET_CURRENT_DESKTOP"),
870 					     0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
871 					     &bytes_after, (gpointer) &current_desktop);
872 		err = gdk_x11_display_error_trap_pop (display);
873 
874 		if (err != Success || result != Success)
875 			return ret;
876 
877 		if (type == XA_CARDINAL && format == 32 && nitems > 0)
878 			ret = current_desktop[0];
879 
880 		XFree (current_desktop);
881 		return ret;
882 	}
883 	else
884 		/* FIXME: on mac etc proably there are native APIs
885 		 * to get the current workspace etc */
886 		return 0;
887 }
888 
889 /**
890  * pluma_utils_get_window_workspace:
891  *
892  * Get the workspace the window is on
893  *
894  * This function gets the workspace that the #GtkWindow is visible on,
895  * it returns PLUMA_ALL_WORKSPACES if the window is sticky, or if
896  * the window manager doesn support this function
897  */
898 guint
pluma_utils_get_window_workspace(GtkWindow * gtkwindow)899 pluma_utils_get_window_workspace (GtkWindow *gtkwindow)
900 {
901 
902 	if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
903 	{
904 		GdkWindow *window;
905 		GdkDisplay *display;
906 		Atom type;
907 		gint format;
908 		gulong nitems;
909 		gulong bytes_after;
910 		guint *workspace;
911 		gint err, result;
912 		guint ret = PLUMA_ALL_WORKSPACES;
913 
914 		g_return_val_if_fail (GTK_IS_WINDOW (gtkwindow), 0);
915 		g_return_val_if_fail (gtk_widget_get_realized (GTK_WIDGET (gtkwindow)), 0);
916 
917 		window = gtk_widget_get_window (GTK_WIDGET (gtkwindow));
918 		display = gdk_window_get_display (window);
919 
920 		gdk_x11_display_error_trap_push (display);
921 		result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (window),
922 					     gdk_x11_get_xatom_by_name_for_display (display, "_NET_WM_DESKTOP"),
923 					     0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
924 					     &bytes_after, (gpointer) &workspace);
925 		err = gdk_x11_display_error_trap_pop (display);
926 
927 		if (err != Success || result != Success)
928 			return ret;
929 
930 		if (type == XA_CARDINAL && format == 32 && nitems > 0)
931 			ret = workspace[0];
932 
933 		XFree (workspace);
934 		return ret;
935 	}
936 	else
937 		/* FIXME: on mac etc proably there are native APIs
938 		 * to get the current workspace etc */
939 		return 0;
940 }
941 
942 /**
943  * pluma_utils_get_current_viewport:
944  *
945  * Get the current viewport origin
946  *
947  * Get the currently visible viewport origin for the #GdkScreen.
948  *
949  * If the X11 window property isn't found, (0, 0) is returned.
950  */
951 void
pluma_utils_get_current_viewport(GdkScreen * screen,gint * x,gint * y)952 pluma_utils_get_current_viewport (GdkScreen    *screen,
953 				  gint         *x,
954 				  gint         *y)
955 {
956 	if (GDK_IS_X11_DISPLAY (gdk_display_get_default ()))
957 	{
958 		GdkWindow *root_win;
959 		GdkDisplay *display;
960 		Atom type;
961 		gint format;
962 		gulong nitems;
963 		gulong bytes_after;
964 		gulong *coordinates;
965 		gint err, result;
966 
967 		g_return_if_fail (GDK_IS_SCREEN (screen));
968 		g_return_if_fail (x != NULL && y != NULL);
969 
970 		/* Default values for the viewport origin */
971 		*x = 0;
972 		*y = 0;
973 
974 		root_win = gdk_screen_get_root_window (screen);
975 		display = gdk_screen_get_display (screen);
976 
977 		gdk_x11_display_error_trap_push (display);
978 		result = XGetWindowProperty (GDK_DISPLAY_XDISPLAY (display), GDK_WINDOW_XID (root_win),
979 					     gdk_x11_get_xatom_by_name_for_display (display, "_NET_DESKTOP_VIEWPORT"),
980 					     0, G_MAXLONG, False, XA_CARDINAL, &type, &format, &nitems,
981 					     &bytes_after, (void*) &coordinates);
982 		err = gdk_x11_display_error_trap_pop (display);
983 
984 		if (err != Success || result != Success)
985 			return;
986 
987 		if (type != XA_CARDINAL || format != 32 || nitems < 2)
988 		{
989 			XFree (coordinates);
990 			return;
991 		}
992 
993 		*x = coordinates[0];
994 		*y = coordinates[1];
995 		XFree (coordinates);
996 	}
997 	else
998 	/* FIXME: on mac etc proably there are native APIs
999 	 * to get the current workspace etc */
1000 	{
1001 		*x = 0;
1002 		*y = 0;
1003 	}
1004 }
1005 
1006 static gboolean
is_valid_scheme_character(gchar c)1007 is_valid_scheme_character (gchar c)
1008 {
1009 	return g_ascii_isalnum (c) || c == '+' || c == '-' || c == '.';
1010 }
1011 
1012 static gboolean
has_valid_scheme(const gchar * uri)1013 has_valid_scheme (const gchar *uri)
1014 {
1015 	const gchar *p;
1016 
1017 	p = uri;
1018 
1019 	if (!is_valid_scheme_character (*p)) {
1020 		return FALSE;
1021 	}
1022 
1023 	do {
1024 		p++;
1025 	} while (is_valid_scheme_character (*p));
1026 
1027 	return *p == ':';
1028 }
1029 
1030 gboolean
pluma_utils_is_valid_uri(const gchar * uri)1031 pluma_utils_is_valid_uri (const gchar *uri)
1032 {
1033 	const guchar *p;
1034 
1035 	if (uri == NULL)
1036 		return FALSE;
1037 
1038 	if (!has_valid_scheme (uri))
1039 		return FALSE;
1040 
1041 	/* We expect to have a fully valid set of characters */
1042 	for (p = (const guchar *)uri; *p; p++) {
1043 		if (*p == '%')
1044 		{
1045 			++p;
1046 			if (!g_ascii_isxdigit (*p))
1047 				return FALSE;
1048 
1049 			++p;
1050 			if (!g_ascii_isxdigit (*p))
1051 				return FALSE;
1052 		}
1053 		else
1054 		{
1055 			if (*p <= 32 || *p >= 128)
1056 				return FALSE;
1057 		}
1058 	}
1059 
1060 	return TRUE;
1061 }
1062 
1063 static GtkWidget *
handle_builder_error(const gchar * message,...)1064 handle_builder_error (const gchar *message, ...)
1065 {
1066 	GtkWidget *label;
1067 	gchar *msg;
1068 	gchar *msg_plain;
1069 	va_list args;
1070 
1071 	va_start (args, message);
1072 	msg_plain = g_strdup_vprintf (message, args);
1073 	va_end (args);
1074 
1075 	label = gtk_label_new (NULL);
1076 	gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
1077 
1078 	msg = g_strconcat ("<span size=\"large\" weight=\"bold\">",
1079 			   msg_plain, "</span>\n\n",
1080 			   _("Please check your installation."),
1081 			   NULL);
1082 
1083 	gtk_label_set_markup (GTK_LABEL (label), msg);
1084 
1085 	g_free (msg_plain);
1086 	g_free (msg);
1087 
1088 	gtk_widget_set_margin_start (label, 5);
1089 	gtk_widget_set_margin_end (label, 5);
1090 	gtk_widget_set_margin_top (label, 5);
1091 	gtk_widget_set_margin_bottom (label, 5);
1092 
1093 	return label;
1094 }
1095 
1096 /* FIXME this is an issue for introspection */
1097 /**
1098  * pluma_utils_get_ui_objects:
1099  * @filename: the path to the gtk builder file
1100  * @root_objects: a %NULL terminated list of root objects to load or NULL to
1101  *                load all objects
1102  * @error_widget: a pointer were a #GtkLabel
1103  * @object_name: the name of the first object
1104  * @...: a pointer were the first object is returned, followed by more
1105  *       name / object pairs and terminated by %NULL.
1106  *
1107  * This function gets the requested objects from a GtkBuilder ui file. In case
1108  * of error it returns %FALSE and sets error_widget to a GtkLabel containing
1109  * the error message to display.
1110  *
1111  * Returns: %FALSE if an error occurs, %TRUE on success.
1112  */
1113 gboolean
pluma_utils_get_ui_objects(const gchar * filename,gchar ** root_objects,GtkWidget ** error_widget,const gchar * object_name,...)1114 pluma_utils_get_ui_objects (const gchar  *filename,
1115 			    gchar       **root_objects,
1116 			    GtkWidget   **error_widget,
1117 			    const gchar  *object_name,
1118 			    ...)
1119 {
1120 
1121 	GtkBuilder *builder;
1122 	va_list args;
1123 	const gchar *name;
1124 	GError *error = NULL;
1125 	gchar *filename_markup;
1126 	gboolean ret = TRUE;
1127 
1128 	g_return_val_if_fail (filename != NULL, FALSE);
1129 	g_return_val_if_fail (error_widget != NULL, FALSE);
1130 	g_return_val_if_fail (object_name != NULL, FALSE);
1131 
1132 	filename_markup = g_markup_printf_escaped ("<i>%s</i>", filename);
1133 	*error_widget = NULL;
1134 
1135 	builder = gtk_builder_new ();
1136 
1137 	if (root_objects != NULL)
1138 	{
1139 		gtk_builder_add_objects_from_file (builder,
1140 						   filename,
1141 						   root_objects,
1142 						   &error);
1143 	}
1144 	else
1145 	{
1146 		gtk_builder_add_from_file (builder,
1147 					   filename,
1148 					   &error);
1149 	}
1150 
1151 	if (error != NULL)
1152 	{
1153 		*error_widget = handle_builder_error (_("Unable to open UI file %s. Error: %s"),
1154 						      filename_markup,
1155 						      error->message);
1156 		g_error_free (error);
1157 		g_free (filename_markup);
1158 		g_object_unref (builder);
1159 
1160 		return FALSE;
1161 	}
1162 
1163 	va_start (args, object_name);
1164 	for (name = object_name; name; name = va_arg (args, const gchar *) )
1165 	{
1166 		GObject **gobj;
1167 
1168 		gobj = va_arg (args, GObject **);
1169 		*gobj = gtk_builder_get_object (builder, name);
1170 
1171 		if (!*gobj)
1172 		{
1173 			*error_widget = handle_builder_error (_("Unable to find the object '%s' inside file %s."),
1174 							      name,
1175 							      filename_markup),
1176 			ret = FALSE;
1177 			break;
1178 		}
1179 
1180 		/* we return a new ref for the root objects,
1181 		 * the others are already reffed by their parent root object */
1182 		if (root_objects != NULL)
1183 		{
1184 			gint i;
1185 
1186 			for (i = 0; root_objects[i] != NULL; ++i)
1187 			{
1188 				if ((strcmp (name, root_objects[i]) == 0))
1189 				{
1190 					g_object_ref (*gobj);
1191 				}
1192 			}
1193 		}
1194 	}
1195 	va_end (args);
1196 
1197 	g_free (filename_markup);
1198 	g_object_unref (builder);
1199 
1200 	return ret;
1201 }
1202 
1203 gchar *
pluma_utils_make_canonical_uri_from_shell_arg(const gchar * str)1204 pluma_utils_make_canonical_uri_from_shell_arg (const gchar *str)
1205 {
1206 	GFile *gfile;
1207 	gchar *uri;
1208 
1209 	g_return_val_if_fail (str != NULL, NULL);
1210 	g_return_val_if_fail (*str != '\0', NULL);
1211 
1212 	/* Note for the future:
1213 	 * FIXME: is still still relevant?
1214 	 *
1215 	 * <federico> paolo: and flame whoever tells
1216 	 * you that file:///mate/test_files/hëllò
1217 	 * doesn't work --- that's not a valid URI
1218 	 *
1219 	 * <paolo> federico: well, another solution that
1220 	 * does not requires patch to _from_shell_args
1221 	 * is to check that the string returned by it
1222 	 * contains only ASCII chars
1223 	 * <federico> paolo: hmmmm, isn't there
1224 	 * mate_vfs_is_uri_valid() or something?
1225 	 * <paolo>: I will use pluma_utils_is_valid_uri ()
1226 	 *
1227 	 */
1228 
1229 	gfile = g_file_new_for_commandline_arg (str);
1230 	uri = g_file_get_uri (gfile);
1231 	g_object_unref (gfile);
1232 
1233 	if (pluma_utils_is_valid_uri (uri))
1234 		return uri;
1235 
1236 	g_free (uri);
1237 	return NULL;
1238 }
1239 
1240 /**
1241  * pluma_utils_file_has_parent:
1242  * @gfile: the GFile to check the parent for
1243  *
1244  * Return %TRUE if the specified gfile has a parent (is not the root), %FALSE
1245  * otherwise
1246  */
1247 gboolean
pluma_utils_file_has_parent(GFile * gfile)1248 pluma_utils_file_has_parent (GFile *gfile)
1249 {
1250 	GFile *parent;
1251 	gboolean ret;
1252 
1253 	parent = g_file_get_parent (gfile);
1254 	ret = parent != NULL;
1255 
1256 	if (parent)
1257 		g_object_unref (parent);
1258 
1259 	return ret;
1260 }
1261 
1262 /**
1263  * pluma_utils_basename_for_display:
1264  * @uri: uri for which the basename should be displayed
1265  *
1266  * Return the basename of a file suitable for display to users.
1267  */
1268 gchar *
pluma_utils_basename_for_display(gchar const * uri)1269 pluma_utils_basename_for_display (gchar const *uri)
1270 {
1271 	gchar *name;
1272 	GFile *gfile;
1273 	gchar *hn;
1274 
1275 	g_return_val_if_fail (uri != NULL, NULL);
1276 
1277 	gfile = g_file_new_for_uri (uri);
1278 
1279 	/* First, try to query the display name, but only on local files */
1280 	if (g_file_has_uri_scheme (gfile, "file"))
1281 	{
1282 		GFileInfo *info;
1283 		info = g_file_query_info (gfile,
1284 					  G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME,
1285 					  G_FILE_QUERY_INFO_NONE,
1286 					  NULL,
1287 					  NULL);
1288 
1289 		if (info)
1290 		{
1291 			/* Simply get the display name to use as the basename */
1292 			name = g_strdup (g_file_info_get_display_name (info));
1293 			g_object_unref (info);
1294 		}
1295 		else
1296 		{
1297 			/* This is a local file, and therefore we will use
1298 			 * g_filename_display_basename on the local path */
1299 			gchar *local_path;
1300 
1301 			local_path = g_file_get_path (gfile);
1302 			name = g_filename_display_basename (local_path);
1303 			g_free (local_path);
1304 		}
1305 	}
1306 	else if (pluma_utils_file_has_parent (gfile) || !pluma_utils_decode_uri (uri, NULL, NULL, &hn, NULL, NULL))
1307 	{
1308 		/* For remote files with a parent (so not just http://foo.com)
1309 		   or remote file for which the decoding of the host name fails,
1310 		   use the _parse_name and take basename of that */
1311 		gchar *parse_name;
1312 		gchar *base;
1313 
1314 		parse_name = g_file_get_parse_name (gfile);
1315 		base = g_filename_display_basename (parse_name);
1316 		name = g_uri_unescape_string (base, NULL);
1317 
1318 		g_free (base);
1319 		g_free (parse_name);
1320 	}
1321 	else
1322 	{
1323 		/* display '/ on <host>' using the decoded host */
1324 		gchar *hn_utf8;
1325 
1326 		if  (hn != NULL)
1327 			hn_utf8 = pluma_utils_make_valid_utf8 (hn);
1328 		else
1329 			/* we should never get here */
1330 			hn_utf8 = g_strdup ("?");
1331 
1332 		/* Translators: '/ on <remote-share>' */
1333 		name = g_strdup_printf (_("/ on %s"), hn_utf8);
1334 
1335 		g_free (hn_utf8);
1336 		g_free (hn);
1337 	}
1338 
1339 	g_object_unref (gfile);
1340 
1341 	return name;
1342 }
1343 
1344 /**
1345  * pluma_utils_uri_for_display:
1346  * @uri: uri to be displayed.
1347  *
1348  * Filter, modify, unescape and change @uri to make it appropriate
1349  * for display to users.
1350  *
1351  * This function is a convenient wrapper for g_file_get_parse_name
1352  *
1353  * Return value: a string which represents @uri and can be displayed.
1354  */
1355 gchar *
pluma_utils_uri_for_display(const gchar * uri)1356 pluma_utils_uri_for_display (const gchar *uri)
1357 {
1358 	GFile *gfile;
1359 	gchar *parse_name;
1360 
1361 	gfile = g_file_new_for_uri (uri);
1362 	parse_name = g_file_get_parse_name (gfile);
1363 	g_object_unref (gfile);
1364 
1365 	return parse_name;
1366 }
1367 
1368 /**
1369  * pluma_utils_drop_get_uris:
1370  * @selection_data: the #GtkSelectionData from drag_data_received
1371  *
1372  * Create a list of valid uri's from a uri-list drop.
1373  *
1374  * Return value: (transfer full): a string array which will hold the uris or %NULL if there
1375  *		 were no valid uris. g_strfreev should be used when the
1376  *		 string array is no longer used
1377  */
1378 gchar **
pluma_utils_drop_get_uris(GtkSelectionData * selection_data)1379 pluma_utils_drop_get_uris (GtkSelectionData *selection_data)
1380 {
1381 	gchar **uris;
1382 	gint i;
1383 	gint p = 0;
1384 	gchar **uri_list;
1385 
1386 	uris = g_uri_list_extract_uris ((gchar *) gtk_selection_data_get_data (selection_data));
1387 	uri_list = g_new0(gchar *, g_strv_length (uris) + 1);
1388 
1389 	for (i = 0; uris[i] != NULL; i++)
1390 	{
1391 		gchar *uri;
1392 
1393 		uri = pluma_utils_make_canonical_uri_from_shell_arg (uris[i]);
1394 
1395 		/* Silently ignore malformed URI/filename */
1396 		if (uri != NULL)
1397 			uri_list[p++] = uri;
1398 	}
1399 
1400 	g_strfreev (uris);
1401 
1402 	if (*uri_list == NULL)
1403 	{
1404 		g_free(uri_list);
1405 		return NULL;
1406 	}
1407 
1408 	return uri_list;
1409 }
1410 
1411 static void
null_ptr(gchar ** ptr)1412 null_ptr (gchar **ptr)
1413 {
1414 	if (ptr)
1415 		*ptr = NULL;
1416 }
1417 
1418 /**
1419  * pluma_utils_decode_uri:
1420  * @uri: the uri to decode
1421  * @scheme: (allow-none): return value pointer for the uri's
1422  * scheme (e.g. http, sftp, ...), or %NULL
1423  * @user: (allow-none): return value pointer for the uri user info, or %NULL
1424  * @host: (allow-none): return value pointer for the uri host, or %NULL
1425  * @port: (allow-none): return value pointer for the uri port, or %NULL
1426  * @path: (allow-none): return value pointer for the uri path, or %NULL
1427  *
1428  * Parse and break an uri apart in its individual components like the uri
1429  * scheme, user info, port, host and path. The return value pointer can be
1430  * %NULL to ignore certain parts of the uri. If the function returns %TRUE, then
1431  * all return value pointers should be freed using g_free
1432  *
1433  * Return value: %TRUE if the uri could be properly decoded, %FALSE otherwise.
1434  */
1435 gboolean
pluma_utils_decode_uri(const gchar * uri,gchar ** scheme,gchar ** user,gchar ** host,gchar ** port,gchar ** path)1436 pluma_utils_decode_uri (const gchar  *uri,
1437 			gchar       **scheme,
1438 			gchar       **user,
1439 			gchar       **host,
1440 			gchar       **port,
1441 			gchar       **path
1442 )
1443 {
1444 	/* Largely copied from glib/gio/gdummyfile.c:_g_decode_uri. This
1445 	 * functionality should be in glib/gio, but for now we implement it
1446 	 * ourselves (see bug #546182) */
1447 
1448 	const char *p, *in, *hier_part_start, *hier_part_end;
1449 	char *out;
1450 	char c;
1451 
1452 	/* From RFC 3986 Decodes:
1453 	 * URI = scheme ":" hier-part [ "?" query ] [ "#" fragment ]
1454 	 */
1455 
1456 	p = uri;
1457 
1458 	null_ptr (scheme);
1459 	null_ptr (user);
1460 	null_ptr (port);
1461 	null_ptr (host);
1462 	null_ptr (path);
1463 
1464 	/* Decode scheme:
1465 	 * scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
1466 	 */
1467 
1468 	if (!g_ascii_isalpha (*p))
1469 		return FALSE;
1470 
1471 	while (1)
1472 	{
1473 		c = *p++;
1474 
1475 		if (c == ':')
1476 			break;
1477 
1478 		if (!(g_ascii_isalnum(c) ||
1479 		      c == '+' ||
1480 		      c == '-' ||
1481 		      c == '.'))
1482 			return FALSE;
1483 	}
1484 
1485 	if (scheme)
1486 	{
1487 		*scheme = g_malloc (p - uri);
1488 		out = *scheme;
1489 
1490 		for (in = uri; in < p - 1; in++)
1491 			*out++ = g_ascii_tolower (*in);
1492 
1493 		*out = '\0';
1494 	}
1495 
1496 	hier_part_start = p;
1497 	hier_part_end = p + strlen (p);
1498 
1499 	if (hier_part_start[0] == '/' && hier_part_start[1] == '/')
1500 	{
1501 		const char *authority_start, *authority_end;
1502 		const char *userinfo_start, *userinfo_end;
1503 		const char *host_start, *host_end;
1504 		const char *port_start;
1505 
1506 		authority_start = hier_part_start + 2;
1507 		/* authority is always followed by / or nothing */
1508 		authority_end = memchr (authority_start, '/', hier_part_end - authority_start);
1509 
1510 		if (authority_end == NULL)
1511 			authority_end = hier_part_end;
1512 
1513 		/* 3.2:
1514 		 * authority = [ userinfo "@" ] host [ ":" port ]
1515 		 */
1516 
1517 		userinfo_end = memchr (authority_start, '@', authority_end - authority_start);
1518 
1519 		if (userinfo_end)
1520 		{
1521 			userinfo_start = authority_start;
1522 
1523 			if (user)
1524 				*user = g_uri_unescape_segment (userinfo_start, userinfo_end, NULL);
1525 
1526 			if (user && *user == NULL)
1527 			{
1528 				if (scheme)
1529 					g_free (*scheme);
1530 
1531 				return FALSE;
1532 			}
1533 
1534 			host_start = userinfo_end + 1;
1535 		}
1536 		else
1537 			host_start = authority_start;
1538 
1539 		port_start = memchr (host_start, ':', authority_end - host_start);
1540 
1541 		if (port_start)
1542 		{
1543 			host_end = port_start++;
1544 
1545 			if (port)
1546 				*port = g_strndup (port_start, authority_end - port_start);
1547 		}
1548 		else
1549 			host_end = authority_end;
1550 
1551 		if (host)
1552 			*host = g_strndup (host_start, host_end - host_start);
1553 
1554 		hier_part_start = authority_end;
1555 	}
1556 
1557 	if (path)
1558 		*path = g_uri_unescape_segment (hier_part_start, hier_part_end, "/");
1559 
1560 	return TRUE;
1561 }
1562 
1563 gboolean
pluma_gtk_text_iter_regex_search(const GtkTextIter * iter,const gchar * str,GtkTextSearchFlags flags,GtkTextIter * match_start,GtkTextIter * match_end,const GtkTextIter * limit,gboolean forward_search,gchar ** replace_text)1564 pluma_gtk_text_iter_regex_search (const GtkTextIter *iter,
1565 				  const gchar       *str,
1566 				  GtkTextSearchFlags flags,
1567 				  GtkTextIter       *match_start,
1568 				  GtkTextIter       *match_end,
1569 				  const GtkTextIter *limit,
1570 				  gboolean forward_search,
1571 				  gchar            **replace_text)
1572 {
1573 	GRegex *regex;
1574 	GRegexCompileFlags compile_flags;
1575 	GMatchInfo *match_info;
1576 	gchar *text;
1577 	GtkTextIter *begin_iter;
1578 	GtkTextIter *end_iter;
1579 	gchar *match_string;
1580 	gboolean found;
1581 
1582 	match_string = "";
1583 	compile_flags = G_REGEX_OPTIMIZE | G_REGEX_MULTILINE;
1584 
1585 	if ((flags & GTK_TEXT_SEARCH_CASE_INSENSITIVE) != 0)
1586 		compile_flags |= G_REGEX_CASELESS;
1587 
1588 	regex = g_regex_new (str,compile_flags,0,NULL);
1589 
1590 	if (regex == NULL)
1591 		return FALSE;
1592 
1593 	begin_iter = gtk_text_iter_copy (iter);
1594 
1595 	if (limit == NULL)
1596 	{
1597 		end_iter = gtk_text_iter_copy (begin_iter);
1598 		if (forward_search)
1599 		{
1600 			gtk_text_buffer_get_end_iter (gtk_text_iter_get_buffer(begin_iter),
1601 						      end_iter);
1602 		}
1603 		else
1604 		{
1605 			gtk_text_buffer_get_start_iter (gtk_text_iter_get_buffer (begin_iter),
1606 							end_iter);
1607 		}
1608 	}
1609 	else
1610 	{
1611 		end_iter = gtk_text_iter_copy (limit);
1612 	}
1613 
1614 	if ((flags & GTK_TEXT_SEARCH_TEXT_ONLY) != 0)
1615 	{
1616 		if ((flags & GTK_TEXT_SEARCH_VISIBLE_ONLY) != 0)
1617 			text = gtk_text_iter_get_visible_text (begin_iter, end_iter);
1618 		else
1619 			text = gtk_text_iter_get_text (begin_iter, end_iter);
1620 	}
1621 	else if ((flags & GTK_TEXT_SEARCH_VISIBLE_ONLY) != 0)
1622 		text = gtk_text_iter_get_visible_slice (begin_iter, end_iter);
1623 	else
1624 		text = gtk_text_iter_get_slice (begin_iter, end_iter);
1625 
1626 	found = g_regex_match (regex, text, 0, &match_info);
1627 
1628 	if (!found)
1629 		goto free_resources;
1630 
1631 	if((replace_text != NULL) && (*replace_text != NULL))
1632 	{
1633 		*replace_text = g_match_info_expand_references (match_info,
1634 								*replace_text,
1635 								NULL);
1636 	}
1637 
1638 	while (g_match_info_matches (match_info))
1639 	{
1640 		match_string = g_match_info_fetch (match_info, 0);
1641 
1642 		if (forward_search)
1643 			break;
1644 
1645 		g_match_info_next (match_info, NULL);
1646 	}
1647 
1648 
1649 	if (forward_search)
1650 	{
1651 		gtk_text_iter_forward_search (begin_iter,
1652 					      match_string,
1653 					      flags,
1654 					      match_start,
1655 					      match_end,
1656 					      limit);
1657 	} else {
1658 		gtk_text_iter_backward_search (begin_iter,
1659 					       match_string,
1660 					       flags,
1661 					       match_start,
1662 					       match_end,
1663 					       limit);
1664 	}
1665 
1666 free_resources:
1667 	gtk_text_iter_free (begin_iter);
1668 	gtk_text_iter_free (end_iter);
1669 	g_match_info_free (match_info);
1670 	g_regex_unref (regex);
1671 	return found;
1672 }
1673