1 /*
2  * Nautilus-Actions
3  * A Nautilus extension which offers configurable context menu actions.
4  *
5  * Copyright (C) 2005 The GNOME Foundation
6  * Copyright (C) 2006-2008 Frederic Ruaudel and others (see AUTHORS)
7  * Copyright (C) 2009-2014 Pierre Wieser and others (see AUTHORS)
8  *
9  * Nautilus-Actions is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 2 of
12  * the License, or (at your option) any later version.
13  *
14  * Nautilus-Actions is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with Nautilus-Actions; see the file COPYING. If not, see
21  * <http://www.gnu.org/licenses/>.
22  *
23  * Authors:
24  *   Frederic Ruaudel <grumz@grumz.net>
25  *   Rodrigo Moya <rodrigo@gnome-db.org>
26  *   Pierre Wieser <pwieser@trychlos.org>
27  *   ... and many others (see AUTHORS)
28  */
29 
30 #ifdef HAVE_CONFIG_H
31 #include <config.h>
32 #endif
33 
34 #include <gio/gunixinputstream.h>
35 #include <glib/gi18n.h>
36 #include <gtk/gtk.h>
37 #include <string.h>
38 
39 #include <api/na-core-utils.h>
40 #include <api/na-object-api.h>
41 
42 #include "na-gnome-vfs-uri.h"
43 #include "na-selected-info.h"
44 #include "na-settings.h"
45 #include "na-tokens.h"
46 
47 /* private class data
48  */
49 struct _NATokensClassPrivate {
50 	void *empty;						/* so that gcc -pedantic is happy */
51 };
52 
53 /* private instance data
54  */
55 struct _NATokensPrivate {
56 	gboolean dispose_has_run;
57 	guint    count;
58 	GSList  *uris;
59 	GSList  *filenames;
60 	GSList  *basedirs;
61 	GSList  *basenames;
62 	GSList  *basenames_woext;
63 	GSList  *exts;
64 	GSList  *mimetypes;
65 	gchar   *hostname;
66 	gchar   *username;
67 	guint    port;
68 	gchar   *scheme;
69 };
70 
71 /*  the structure passed to the callback which waits for the end of the child
72  */
73 typedef struct {
74 	gchar   *command;
75 	gboolean is_output_displayed;
76 	gint     child_stdout;
77 	gint     child_stderr;
78 }
79 	ChildStr;
80 
81 static GObjectClass *st_parent_class = NULL;
82 
83 static GType     register_type( void );
84 static void      class_init( NATokensClass *klass );
85 static void      instance_init( GTypeInstance *instance, gpointer klass );
86 static void      instance_dispose( GObject *object );
87 static void      instance_finalize( GObject *object );
88 
89 static void      child_watch_fn( GPid pid, gint status, ChildStr *child_str );
90 static void      display_output( const gchar *command, int fd_stdout, int fd_stderr );
91 static gchar    *display_output_get_content( int fd );
92 static void      execute_action_command( gchar *command, const NAObjectProfile *profile, const NATokens *tokens );
93 static gchar    *get_command_execution_display_output( const gchar *command );
94 static gchar    *get_command_execution_embedded( const gchar *command );
95 static gchar    *get_command_execution_normal( const gchar *command );
96 static gchar    *get_command_execution_terminal( const gchar *command );
97 static gboolean  is_singular_exec( const NATokens *tokens, const gchar *exec );
98 static gchar    *parse_singular( const NATokens *tokens, const gchar *input, guint i, gboolean utf8, gboolean quoted );
99 static GString  *quote_string( GString *input, const gchar *name, gboolean quoted );
100 static GString  *quote_string_list( GString *input, GSList *names, gboolean quoted );
101 
102 GType
na_tokens_get_type(void)103 na_tokens_get_type( void )
104 {
105 	static GType object_type = 0;
106 
107 	if( !object_type ){
108 		object_type = register_type();
109 	}
110 
111 	return( object_type );
112 }
113 
114 static GType
register_type(void)115 register_type( void )
116 {
117 	static const gchar *thisfn = "na_tokens_register_type";
118 	GType type;
119 
120 	static GTypeInfo info = {
121 		sizeof( NATokensClass ),
122 		( GBaseInitFunc ) NULL,
123 		( GBaseFinalizeFunc ) NULL,
124 		( GClassInitFunc ) class_init,
125 		NULL,
126 		NULL,
127 		sizeof( NATokens ),
128 		0,
129 		( GInstanceInitFunc ) instance_init
130 	};
131 
132 	g_debug( "%s", thisfn );
133 
134 	type = g_type_register_static( G_TYPE_OBJECT, "NATokens", &info, 0 );
135 
136 	return( type );
137 }
138 
139 static void
class_init(NATokensClass * klass)140 class_init( NATokensClass *klass )
141 {
142 	static const gchar *thisfn = "na_tokens_class_init";
143 	GObjectClass *object_class;
144 
145 	g_debug( "%s: klass=%p", thisfn, ( void * ) klass );
146 
147 	st_parent_class = g_type_class_peek_parent( klass );
148 
149 	object_class = G_OBJECT_CLASS( klass );
150 	object_class->dispose = instance_dispose;
151 	object_class->finalize = instance_finalize;
152 
153 	klass->private = g_new0( NATokensClassPrivate, 1 );
154 }
155 
156 static void
instance_init(GTypeInstance * instance,gpointer klass)157 instance_init( GTypeInstance *instance, gpointer klass )
158 {
159 	static const gchar *thisfn = "na_tokens_instance_init";
160 	NATokens *self;
161 
162 	g_return_if_fail( NA_IS_TOKENS( instance ));
163 
164 	g_debug( "%s: instance=%p (%s), klass=%p",
165 			thisfn, ( void * ) instance, G_OBJECT_TYPE_NAME( instance ), ( void * ) klass );
166 
167 	self = NA_TOKENS( instance );
168 
169 	self->private = g_new0( NATokensPrivate, 1 );
170 
171 	self->private->uris = NULL;
172 	self->private->filenames = NULL;
173 	self->private->basedirs = NULL;
174 	self->private->basenames = NULL;
175 	self->private->basenames_woext = NULL;
176 	self->private->exts = NULL;
177 	self->private->mimetypes = NULL;
178 	self->private->hostname = NULL;
179 	self->private->username = NULL;
180 	self->private->port = 0;
181 	self->private->scheme = NULL;
182 
183 	self->private->dispose_has_run = FALSE;
184 }
185 
186 static void
instance_dispose(GObject * object)187 instance_dispose( GObject *object )
188 {
189 	static const gchar *thisfn = "na_tokens_instance_dispose";
190 	NATokens *self;
191 
192 	g_return_if_fail( NA_IS_TOKENS( object ));
193 
194 	self = NA_TOKENS( object );
195 
196 	if( !self->private->dispose_has_run ){
197 
198 		g_debug( "%s: object=%p (%s)", thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ));
199 
200 		self->private->dispose_has_run = TRUE;
201 
202 		if( G_OBJECT_CLASS( st_parent_class )->dispose ){
203 			G_OBJECT_CLASS( st_parent_class )->dispose( object );
204 		}
205 	}
206 }
207 
208 static void
instance_finalize(GObject * object)209 instance_finalize( GObject *object )
210 {
211 	static const gchar *thisfn = "na_tokens_instance_finalize";
212 	NATokens *self;
213 
214 	g_return_if_fail( NA_IS_TOKENS( object ));
215 
216 	g_debug( "%s: object=%p (%s)", thisfn, ( void * ) object, G_OBJECT_TYPE_NAME( object ));
217 
218 	self = NA_TOKENS( object );
219 
220 	g_free( self->private->scheme );
221 	g_free( self->private->username );
222 	g_free( self->private->hostname );
223 	na_core_utils_slist_free( self->private->mimetypes );
224 	na_core_utils_slist_free( self->private->exts );
225 	na_core_utils_slist_free( self->private->basenames_woext );
226 	na_core_utils_slist_free( self->private->basenames );
227 	na_core_utils_slist_free( self->private->basedirs );
228 	na_core_utils_slist_free( self->private->filenames );
229 	na_core_utils_slist_free( self->private->uris );
230 
231 	g_free( self->private );
232 
233 	/* chain call to parent class */
234 	if( G_OBJECT_CLASS( st_parent_class )->finalize ){
235 		G_OBJECT_CLASS( st_parent_class )->finalize( object );
236 	}
237 }
238 
239 /*
240  * na_tokens_new_for_example:
241  *
242  * Returns: a new #NATokens object initialized with fake values for two
243  * regular files, in order to be used as an example of an expanded command
244  * line.
245  */
246 NATokens *
na_tokens_new_for_example(void)247 na_tokens_new_for_example( void )
248 {
249 	NATokens *tokens;
250 	const gchar *ex_uri1 = _( "file:///path/to/file1.mid" );
251 	const gchar *ex_uri2 = _( "file:///path/to/file2.jpeg" );
252 	const gchar *ex_mimetype1 = _( "audio/x-midi" );
253 	const gchar *ex_mimetype2 = _( "image/jpeg" );
254 	const guint  ex_port = 8080;
255 	const gchar *ex_host = _( "test.example.net" );
256 	const gchar *ex_user = _( "user" );
257 	NAGnomeVFSURI *vfs;
258 	gchar *dirname, *bname, *bname_woext, *ext;
259 	GSList *is;
260 	gboolean first;
261 
262 	tokens = g_object_new( NA_TYPE_TOKENS, NULL );
263 	first = TRUE;
264 	tokens->private->count = 2;
265 
266 	tokens->private->uris = g_slist_append( tokens->private->uris, g_strdup( ex_uri1 ));
267 	tokens->private->uris = g_slist_append( tokens->private->uris, g_strdup( ex_uri2 ));
268 
269 	for( is = tokens->private->uris ; is ; is = is->next ){
270 		vfs = g_new0( NAGnomeVFSURI, 1 );
271 		na_gnome_vfs_uri_parse( vfs, is->data );
272 
273 		tokens->private->filenames = g_slist_append( tokens->private->filenames, g_strdup( vfs->path ));
274 		dirname = g_path_get_dirname( vfs->path );
275 		tokens->private->basedirs = g_slist_append( tokens->private->basedirs, dirname );
276 		bname = g_path_get_basename( vfs->path );
277 		tokens->private->basenames = g_slist_append( tokens->private->basenames, bname );
278 		na_core_utils_dir_split_ext( bname, &bname_woext, &ext );
279 		tokens->private->basenames_woext = g_slist_append( tokens->private->basenames_woext, bname_woext );
280 		tokens->private->exts = g_slist_append( tokens->private->exts, ext );
281 
282 		if( first ){
283 			tokens->private->scheme = g_strdup( vfs->scheme );
284 			first = FALSE;
285 		}
286 
287 		na_gnome_vfs_uri_free( vfs );
288 	}
289 
290 	tokens->private->mimetypes = g_slist_append( tokens->private->mimetypes, g_strdup( ex_mimetype1 ));
291 	tokens->private->mimetypes = g_slist_append( tokens->private->mimetypes, g_strdup( ex_mimetype2 ));
292 
293 	tokens->private->hostname = g_strdup( ex_host );
294 	tokens->private->username = g_strdup( ex_user );
295 	tokens->private->port = ex_port;
296 
297 	return( tokens );
298 }
299 
300 /*
301  * na_tokens_new_from_selection:
302  * @selection: a #GList list of #NASelectedInfo objects.
303  *
304  * Returns: a new #NATokens object which holds all possible tokens.
305  */
306 NATokens *
na_tokens_new_from_selection(GList * selection)307 na_tokens_new_from_selection( GList *selection )
308 {
309 	static const gchar *thisfn = "na_tokens_new_from_selection";
310 	NATokens *tokens;
311 	GList *it;
312 	gchar *uri, *filename, *basedir, *basename, *bname_woext, *ext, *mimetype;
313 	gboolean first;
314 
315 	g_debug( "%s: selection=%p (count=%d)", thisfn, ( void * ) selection, g_list_length( selection ));
316 
317 	first = TRUE;
318 	tokens = g_object_new( NA_TYPE_TOKENS, NULL );
319 
320 	tokens->private->count = g_list_length( selection );
321 
322 	for( it = selection ; it ; it = it->next ){
323 		mimetype = na_selected_info_get_mime_type( NA_SELECTED_INFO( it->data ));
324 
325 		uri = na_selected_info_get_uri( NA_SELECTED_INFO( it->data ));
326 		filename = na_selected_info_get_path( NA_SELECTED_INFO( it->data ));
327 		basedir = na_selected_info_get_dirname( NA_SELECTED_INFO( it->data ));
328 		basename = na_selected_info_get_basename( NA_SELECTED_INFO( it->data ));
329 		na_core_utils_dir_split_ext( basename, &bname_woext, &ext );
330 
331 		if( first ){
332 			tokens->private->hostname = na_selected_info_get_uri_host( NA_SELECTED_INFO( it->data ));
333 			tokens->private->username = na_selected_info_get_uri_user( NA_SELECTED_INFO( it->data ));
334 			tokens->private->port = na_selected_info_get_uri_port( NA_SELECTED_INFO( it->data ));
335 			tokens->private->scheme = na_selected_info_get_uri_scheme( NA_SELECTED_INFO( it->data ));
336 			first = FALSE;
337 		}
338 
339 		tokens->private->uris = g_slist_append( tokens->private->uris, uri );
340 		tokens->private->filenames = g_slist_append( tokens->private->filenames, filename );
341 		tokens->private->basedirs = g_slist_append( tokens->private->basedirs, basedir );
342 		tokens->private->basenames = g_slist_append( tokens->private->basenames, basename );
343 		tokens->private->basenames_woext = g_slist_append( tokens->private->basenames_woext, bname_woext );
344 		tokens->private->exts = g_slist_append( tokens->private->exts, ext );
345 		tokens->private->mimetypes = g_slist_append( tokens->private->mimetypes, mimetype );
346 	}
347 
348 	return( tokens );
349 }
350 
351 /*
352  * na_tokens_parse_for_display:
353  * @tokens: a #NATokens object.
354  * @string: the input string, may or may not contain tokens.
355  * @utf8: whether the @input string is UTF-8 encoded, or a standard ASCII string.
356  *
357  * Expands the parameters in the given string.
358  *
359  * This expanded string is meant to be displayed only (not executed) as
360  * filenames are not shell-quoted.
361  *
362  * Returns: a copy of @input string with tokens expanded, as a newly
363  * allocated string which should be g_free() by the caller.
364  */
365 gchar *
na_tokens_parse_for_display(const NATokens * tokens,const gchar * string,gboolean utf8)366 na_tokens_parse_for_display( const NATokens *tokens, const gchar *string, gboolean utf8 )
367 {
368 	return( parse_singular( tokens, string, 0, utf8, FALSE ));
369 }
370 
371 /*
372  * na_tokens_execute_action:
373  * @tokens: a #NATokens object.
374  * @profile: the #NAObjectProfile to be executed.
375  *
376  * Execute the given action, regarding the context described by @tokens.
377  */
378 void
na_tokens_execute_action(const NATokens * tokens,const NAObjectProfile * profile)379 na_tokens_execute_action( const NATokens *tokens, const NAObjectProfile *profile )
380 {
381 	gchar *path, *parameters, *exec;
382 	gboolean singular;
383 	guint i;
384 	gchar *command;
385 
386 	path = na_object_get_path( profile );
387 	parameters = na_object_get_parameters( profile );
388 	exec = g_strdup_printf( "%s %s", path, parameters );
389 	g_free( parameters );
390 	g_free( path );
391 
392 	singular = is_singular_exec( tokens, exec );
393 
394 	if( singular ){
395 		for( i = 0 ; i < tokens->private->count ; ++i ){
396 			command = parse_singular( tokens, exec, i, FALSE, TRUE );
397 			execute_action_command( command, profile, tokens );
398 			g_free( command );
399 		}
400 
401 	} else {
402 		command = parse_singular( tokens, exec, 0, FALSE, TRUE );
403 		execute_action_command( command, profile, tokens );
404 		g_free( command );
405 	}
406 
407 	g_free( exec );
408 }
409 
410 static void
child_watch_fn(GPid pid,gint status,ChildStr * child_str)411 child_watch_fn( GPid pid, gint status, ChildStr *child_str )
412 {
413 	static const gchar *thisfn = "na_tokens_child_watch_fn";
414 
415 	g_debug( "%s: pid=%u, status=%d", thisfn, ( guint ) pid, status );
416 	g_spawn_close_pid( pid );
417 	if( child_str->is_output_displayed ){
418 		display_output( child_str->command, child_str->child_stdout, child_str->child_stderr );
419 	}
420 	g_free( child_str->command );
421 	g_free( child_str );
422 }
423 
424 static void
display_output(const gchar * command,int fd_stdout,int fd_stderr)425 display_output( const gchar *command, int fd_stdout, int fd_stderr )
426 {
427 	GtkWidget *dialog;
428 	gchar *std_output, *std_error;
429 
430 	dialog = gtk_message_dialog_new_with_markup(
431 			NULL, GTK_DIALOG_MODAL, GTK_MESSAGE_INFO, GTK_BUTTONS_OK, "<b>%s</b>", _( "Output of the run command" ));
432 	g_object_set( G_OBJECT( dialog ) , "title", PACKAGE_NAME, NULL );
433 
434 	std_output = display_output_get_content( fd_stdout );
435 	std_error = display_output_get_content( fd_stderr );
436 
437 	gtk_message_dialog_format_secondary_markup( GTK_MESSAGE_DIALOG( dialog ),
438 			"<b>%s</b>\n%s\n\n<b>%s</b>\n%s\n\n<b>%s</b>\n%s\n\n",
439 					_( "Run command:" ), command,
440 					_( "Standard output:" ), std_output,
441 					_( "Standard error:" ), std_error );
442 
443 	gtk_dialog_run( GTK_DIALOG( dialog ));
444 	gtk_widget_destroy( dialog );
445 
446 	g_free( std_output );
447 	g_free( std_error );
448 }
449 
450 static gchar *
display_output_get_content(int fd)451 display_output_get_content( int fd )
452 {
453 	static const gchar *thisfn = "na_tokens_display_output_get_content";
454 	GInputStream *stream;
455 	GString *string;
456 	gchar buf[1024];
457 	GError *error;
458 	gchar *msg;
459 
460 	string = g_string_new( "" );
461 	memset( buf, '\0', sizeof( buf ));
462 
463 	if( fd > 0 ){
464 		stream = g_unix_input_stream_new( fd, TRUE );
465 		error = NULL;
466 
467 		while( g_input_stream_read( stream, buf, sizeof( buf )-1, NULL, &error )){
468 			string = g_string_append( string, buf );
469 			memset( buf, '\0', sizeof( buf ));
470 		}
471 		if( error ){
472 			g_warning( "%s: g_input_stream_read: %s", thisfn, error->message );
473 			g_error_free( error );
474 		}
475 		g_input_stream_close( stream, NULL, NULL );
476 	}
477 
478 	msg = g_locale_to_utf8( string->str, -1, NULL, NULL, NULL );
479 	g_string_free( string, TRUE );
480 
481 	return( msg );
482 }
483 
484 /*
485  * Execution environment:
486  * - Normal: just execute the specified command
487  * - Terminal: use the user preference to have a terminal which stays openeded
488  * - Embedded: id. Terminal
489  * - DisplayOutput: execute in a shell
490  */
491 static void
execute_action_command(gchar * command,const NAObjectProfile * profile,const NATokens * tokens)492 execute_action_command( gchar *command, const NAObjectProfile *profile, const NATokens *tokens )
493 {
494 	static const gchar *thisfn = "nautilus_actions_execute_action_command";
495 	GError *error;
496 	gchar *execution_mode, *run_command;
497 	gchar **argv;
498 	gint argc;
499 	gchar *wdir, *wdir_nq;
500 	GPid child_pid;
501 	ChildStr *child_str;
502 
503 	g_debug( "%s: profile=%p", thisfn, ( void * ) profile );
504 
505 	error = NULL;
506 	run_command = NULL;
507 	child_str = g_new0( ChildStr, 1 );
508 	child_pid = ( GPid ) 0;
509 	execution_mode = na_object_get_execution_mode( profile );
510 
511 	if( !strcmp( execution_mode, "Normal" )){
512 		run_command = get_command_execution_normal( command );
513 
514 	} else if( !strcmp( execution_mode, "Terminal" )){
515 		run_command = get_command_execution_terminal( command );
516 
517 	} else if( !strcmp( execution_mode, "Embedded" )){
518 		run_command = get_command_execution_embedded( command );
519 
520 	} else if( !strcmp( execution_mode, "DisplayOutput" )){
521 		child_str->is_output_displayed = TRUE;
522 		run_command = get_command_execution_display_output( command );
523 
524 	} else {
525 		g_warning( "%s: unknown execution mode: %s", thisfn, execution_mode );
526 	}
527 
528 	if( run_command ){
529 		child_str->command = g_strdup( run_command );
530 
531 		if( !g_shell_parse_argv( run_command, &argc, &argv, &error )){
532 			g_warning( "%s: g_shell_parse_argv: %s", thisfn, error->message );
533 			g_error_free( error );
534 
535 		} else {
536 			wdir = na_object_get_working_dir( profile );
537 			wdir_nq = parse_singular( tokens, wdir, 0, FALSE, FALSE );
538 			g_debug( "%s: run_command=%s, wdir=%s", thisfn, run_command, wdir_nq );
539 
540 			/* it appears that at least mplayer does not support g_spawn_async_with_pipes
541 			 * (at least when not run in '-quiet' mode) while, e.g., totem and vlc rightly
542 			 * support this function
543 			 * So only use g_spawn_async_with_pipes when we really need to get back
544 			 * the content of output and error streams
545 			 * See https://bugzilla.gnome.org/show_bug.cgi?id=644289.
546 			 */
547 			if( child_str->is_output_displayed ){
548 				g_spawn_async_with_pipes(
549 						wdir_nq,
550 						argv,
551 						NULL,
552 						G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
553 						NULL,
554 						NULL,
555 						&child_pid,
556 						NULL,
557 						&child_str->child_stdout,
558 						&child_str->child_stderr,
559 						&error );
560 
561 			} else {
562 				g_spawn_async(
563 						wdir_nq,
564 						argv,
565 						NULL,
566 						G_SPAWN_SEARCH_PATH | G_SPAWN_DO_NOT_REAP_CHILD,
567 						NULL,
568 						NULL,
569 						&child_pid,
570 						&error );
571 			}
572 
573 			if( error ){
574 				g_warning( "%s: g_spawn_async: %s", thisfn, error->message );
575 				g_error_free( error );
576 				child_pid = ( GPid ) 0;
577 
578 			} else {
579 				g_child_watch_add( child_pid, ( GChildWatchFunc ) child_watch_fn, child_str );
580 			}
581 
582 			g_free( wdir );
583 			g_free( wdir_nq );
584 			g_strfreev( argv );
585 		}
586 
587 		g_free( run_command );
588 	}
589 
590 	g_free( execution_mode );
591 
592 	if( child_pid == ( GPid ) 0 ){
593 		g_free( child_str->command );
594 		g_free( child_str );
595 	}
596 }
597 
598 static gchar *
get_command_execution_display_output(const gchar * command)599 get_command_execution_display_output( const gchar *command )
600 {
601 	static const gchar *bin_sh = "/bin/sh -c COMMAND";
602 	return( na_tokens_command_for_terminal( bin_sh, command ));
603 }
604 
605 static gchar *
get_command_execution_embedded(const gchar * command)606 get_command_execution_embedded( const gchar *command )
607 {
608 	return( get_command_execution_terminal( command ));
609 }
610 
611 static gchar *
get_command_execution_normal(const gchar * command)612 get_command_execution_normal( const gchar *command )
613 {
614 	return( g_strdup( command ));
615 }
616 
617 static gchar *
get_command_execution_terminal(const gchar * command)618 get_command_execution_terminal( const gchar *command )
619 {
620 	gchar *run_command;
621 	gchar *pattern;
622 
623 	pattern = na_settings_get_string( NA_IPREFS_TERMINAL_PATTERN, NULL, NULL );
624 	run_command = na_tokens_command_for_terminal( pattern, command );
625 	g_free( pattern );
626 
627 	return( run_command );
628 }
629 
630 /**
631  * na_tokens_command_for_terminal:
632  * @pattern: the command pattern; should include a 'COMMAND' keyword
633  * @command: the command to be actually run in the terminal
634  *
635  * Returns: the command to be run, as a newly allocated string which should
636  * be g_free() by the caller.
637  */
638 gchar *
na_tokens_command_for_terminal(const gchar * pattern,const gchar * command)639 na_tokens_command_for_terminal( const gchar *pattern, const gchar *command )
640 {
641 	gchar *run_command;
642 	gchar *quoted;
643 
644 	if( pattern && strlen( pattern )){
645 		quoted = g_shell_quote( command );
646 		run_command = na_core_utils_str_subst( pattern, "COMMAND", quoted );
647 		g_free( quoted );
648 
649 	} else {
650 		run_command = g_strdup( command );
651 	}
652 
653 	return( run_command );
654 }
655 
656 /*
657  * na_tokens_is_singular_exec:
658  * @tokens: the current #NATokens object.
659  * @exec: the to be executed command-line before having been parsed
660  *
661  * Returns: %TRUE if the first relevant parameter found in @exec
662  * command-line is of singular form, %FALSE else.
663  */
664 static gboolean
is_singular_exec(const NATokens * tokens,const gchar * exec)665 is_singular_exec( const NATokens *tokens, const gchar *exec )
666 {
667 	gboolean singular;
668 	gboolean found;
669 	gchar *iter;
670 
671 	singular = FALSE;
672 	found = FALSE;
673 	iter = ( gchar * ) exec;
674 
675 	while(( iter = g_strstr_len( iter, -1, "%" )) != NULL && !found ){
676 
677 		switch( iter[1] ){
678 			case 'b':
679 			case 'd':
680 			case 'f':
681 			case 'm':
682 			case 'o':
683 			case 'u':
684 			case 'w':
685 			case 'x':
686 				found = TRUE;
687 				singular = TRUE;
688 				break;
689 
690 			case 'B':
691 			case 'D':
692 			case 'F':
693 			case 'M':
694 			case 'O':
695 			case 'U':
696 			case 'W':
697 			case 'X':
698 				found = TRUE;
699 				singular = FALSE;
700 				break;
701 
702 			/* all other parameters are irrelevant according to DES-EMA
703 			 * c: selection count
704 			 * h: hostname
705 			 * n: username
706 			 * p: port
707 			 * s: scheme
708 			 * %: %
709 			 */
710 		}
711 
712 		iter += 2;			/* skip the % sign and the character after */
713 	}
714 
715 	return( singular );
716 }
717 
718 /*
719  * parse_singular:
720  * @tokens: a #NATokens object.
721  * @input: the input string, may or may not contain tokens.
722  * @i: the number of the iteration in a multiple selection, starting with zero.
723  * @utf8: whether the @input string is UTF-8 encoded, or a standard ASCII
724  *  string.
725  * @quoted: whether the filenames have to be quoted (should be %TRUE when
726  *  about to execute a command).
727  *
728  * A command is said of 'singular form' when its first parameter is not
729  * of plural form. In the case of a multiple selection, singular form
730  * commands are executed one time for each element of the selection
731  *
732  * Returns: a #GSList which contains two fields: the command and its parameters.
733  * The returned #GSList should be na_core_utils_slist_free() by the caller.
734  */
735 static gchar *
parse_singular(const NATokens * tokens,const gchar * input,guint i,gboolean utf8,gboolean quoted)736 parse_singular( const NATokens *tokens, const gchar *input, guint i, gboolean utf8, gboolean quoted )
737 {
738 	GString *output;
739 	gchar *iter, *prev_iter;
740 	const gchar *nth;
741 
742 	output = g_string_new( "" );
743 
744 	/* return NULL if input is NULL
745 	 */
746 	if( !input ){
747 		return( g_string_free( output, TRUE ));
748 	}
749 
750 	/* return an empty string if input is empty
751 	 */
752 	if( utf8 ){
753 		if( !g_utf8_strlen( input, -1 )){
754 			return( g_string_free( output, FALSE ));
755 		}
756 	} else {
757 		if( !strlen( input )){
758 			return( g_string_free( output, FALSE ));
759 		}
760 	}
761 
762 	iter = ( gchar * ) input;
763 	prev_iter = iter;
764 
765 	while(( iter = g_strstr_len( iter, -1, "%" ))){
766 		output = g_string_append_len( output, prev_iter, strlen( prev_iter ) - strlen( iter ));
767 
768 		switch( iter[1] ){
769 			case 'b':
770 				if( tokens->private->basenames ){
771 					nth = ( const gchar * ) g_slist_nth_data( tokens->private->basenames, i );
772 					if( nth ){
773 						output = quote_string( output, nth, quoted );
774 					}
775 				}
776 				break;
777 
778 			case 'B':
779 				if( tokens->private->basenames ){
780 					output = quote_string_list( output, tokens->private->basenames, quoted );
781 				}
782 				break;
783 
784 			case 'c':
785 				g_string_append_printf( output, "%d", tokens->private->count );
786 				break;
787 
788 			case 'd':
789 				if( tokens->private->basedirs ){
790 					nth = ( const gchar * ) g_slist_nth_data( tokens->private->basedirs, i );
791 					if( nth ){
792 						output = quote_string( output, nth, quoted );
793 					}
794 				}
795 				break;
796 
797 			case 'D':
798 				if( tokens->private->basedirs ){
799 					output = quote_string_list( output, tokens->private->basedirs, quoted );
800 				}
801 				break;
802 
803 			case 'f':
804 				if( tokens->private->filenames ){
805 					nth = ( const gchar * ) g_slist_nth_data( tokens->private->filenames, i );
806 					if( nth ){
807 						output = quote_string( output, nth, quoted );
808 					}
809 				}
810 				break;
811 
812 			case 'F':
813 				if( tokens->private->filenames ){
814 					output = quote_string_list( output, tokens->private->filenames, quoted );
815 				}
816 				break;
817 
818 			case 'h':
819 				if( tokens->private->hostname ){
820 					output = quote_string( output, tokens->private->hostname, quoted );
821 				}
822 				break;
823 
824 			/* mimetypes are never quoted
825 			 */
826 			case 'm':
827 				if( tokens->private->mimetypes ){
828 					nth = ( const gchar * ) g_slist_nth_data( tokens->private->mimetypes, i );
829 					if( nth ){
830 						output = quote_string( output, nth, FALSE );
831 					}
832 				}
833 				break;
834 
835 			case 'M':
836 				if( tokens->private->mimetypes ){
837 					output = quote_string_list( output, tokens->private->mimetypes, FALSE );
838 				}
839 				break;
840 
841 			/* no-op operators */
842 			case 'o':
843 			case 'O':
844 				break;
845 
846 			case 'n':
847 				if( tokens->private->username ){
848 					output = quote_string( output, tokens->private->username, quoted );
849 				}
850 				break;
851 
852 			/* port number is never quoted
853 			 */
854 			case 'p':
855 				if( tokens->private->port > 0 ){
856 					g_string_append_printf( output, "%d", tokens->private->port );
857 				}
858 				break;
859 
860 			case 's':
861 				if( tokens->private->scheme ){
862 					output = quote_string( output, tokens->private->scheme, quoted );
863 				}
864 				break;
865 
866 			case 'u':
867 				if( tokens->private->uris ){
868 					nth = ( const gchar * ) g_slist_nth_data( tokens->private->uris, i );
869 					if( nth ){
870 						output = quote_string( output, nth, quoted );
871 					}
872 				}
873 				break;
874 
875 			case 'U':
876 				if( tokens->private->uris ){
877 					output = quote_string_list( output, tokens->private->uris, quoted );
878 				}
879 				break;
880 
881 			case 'w':
882 				if( tokens->private->basenames_woext ){
883 					nth = ( const gchar * ) g_slist_nth_data( tokens->private->basenames_woext, i );
884 					if( nth ){
885 						output = quote_string( output, nth, quoted );
886 					}
887 				}
888 				break;
889 
890 			case 'W':
891 				if( tokens->private->basenames_woext ){
892 					output = quote_string_list( output, tokens->private->basenames_woext, quoted );
893 				}
894 				break;
895 
896 			case 'x':
897 				if( tokens->private->exts ){
898 					nth = ( const gchar * ) g_slist_nth_data( tokens->private->exts, i );
899 					if( nth ){
900 						output = quote_string( output, nth, quoted );
901 					}
902 				}
903 				break;
904 
905 			case 'X':
906 				if( tokens->private->exts ){
907 					output = quote_string_list( output, tokens->private->exts, quoted );
908 				}
909 				break;
910 
911 			/* a percent sign
912 			 */
913 			case '%':
914 				output = g_string_append_c( output, '%' );
915 				break;
916 		}
917 
918 		iter += 2;			/* skip the % sign and the character after */
919 		prev_iter = iter;	/* store the new start of the string */
920 	}
921 
922 	output = g_string_append_len( output, prev_iter, strlen( prev_iter ));
923 
924 	return( g_string_free( output, FALSE ));
925 }
926 
927 static GString *
quote_string(GString * input,const gchar * name,gboolean quoted)928 quote_string( GString *input, const gchar *name, gboolean quoted )
929 {
930 	gchar *tmp;
931 
932 	if( quoted ){
933 		tmp = g_shell_quote( name );
934 		input = g_string_append( input, tmp );
935 		g_free( tmp );
936 
937 	} else {
938 		input = g_string_append( input, name );
939 	}
940 
941 	return( input );
942 }
943 
944 static GString *
quote_string_list(GString * input,GSList * names,gboolean quoted)945 quote_string_list( GString *input, GSList *names, gboolean quoted )
946 {
947 	GSList *it;
948 	gchar *tmp;
949 
950 	if( quoted ){
951 		GSList *quoted_names = NULL;
952 		for( it = names ; it ; it = it->next ){
953 			quoted_names = g_slist_append( quoted_names, g_shell_quote(( const gchar * ) it->data ));
954 		}
955 		tmp = na_core_utils_slist_join_at_end( quoted_names, " " );
956 		na_core_utils_slist_free( quoted_names );
957 
958 	} else {
959 		tmp = na_core_utils_slist_join_at_end( g_slist_reverse( names ), " " );
960 	}
961 
962 	input = g_string_append( input, tmp );
963 	g_free( tmp );
964 
965 	return( input );
966 }
967