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