1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 %                                                                             %
4 %                                                                             %
5 %                                                                             %
6 %                 M   M   AAA    GGGG  IIIII   CCCC  K   K                    %
7 %                 MM MM  A   A  G        I    C      K  K                     %
8 %                 M M M  AAAAA  G GGG    I    C      KKK                      %
9 %                 M   M  A   A  G   G    I    C      K  K                     %
10 %                 M   M  A   A   GGGG  IIIII   CCCC  K   K                    %
11 %                                                                             %
12 %                            CCCC  L      IIIII                               %
13 %                           C      L        I                                 %
14 %                           C      L        I                                 %
15 %                           C      L        I                                 %
16 %                            CCCC  LLLLL  IIIII                               %
17 %                                                                             %
18 %       Perform "Magick" on Images via the Command Line Interface             %
19 %                                                                             %
20 %                             Dragon Computing                                %
21 %                             Anthony Thyssen                                 %
22 %                               January 2012                                  %
23 %                                                                             %
24 %                                                                             %
25 %  Copyright 1999-2021 ImageMagick Studio LLC, a non-profit organization      %
26 %  dedicated to making software imaging solutions freely available.           %
27 %                                                                             %
28 %  You may not use this file except in compliance with the License.  You may  %
29 %  obtain a copy of the License at                                            %
30 %                                                                             %
31 %    https://imagemagick.org/script/license.php                               %
32 %                                                                             %
33 %  Unless required by applicable law or agreed to in writing, software        %
34 %  distributed under the License is distributed on an "AS IS" BASIS,          %
35 %  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   %
36 %  See the License for the specific language governing permissions and        %
37 %  limitations under the License.                                             %
38 %                                                                             %
39 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
40 %
41 %  Read CLI arguments, script files, and pipelines, to provide options that
42 %  manipulate images from many different formats.
43 %
44 */
45 
46 /*
47   Include declarations.
48 */
49 #include "MagickWand/studio.h"
50 #include "MagickWand/MagickWand.h"
51 #include "MagickWand/magick-wand-private.h"
52 #include "MagickWand/wandcli.h"
53 #include "MagickWand/wandcli-private.h"
54 #include "MagickWand/operation.h"
55 #include "MagickWand/magick-cli.h"
56 #include "MagickWand/script-token.h"
57 #include "MagickCore/utility-private.h"
58 #include "MagickCore/exception-private.h"
59 #include "MagickCore/version.h"
60 
61 /* verbose debugging,
62       0 - no debug lines
63       3 - show option details  (better to use -debug Command now)
64       5 - image counts (after option runs)
65 */
66 #define MagickCommandDebug 0
67 
68 
69 /*
70 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
71 %                                                                             %
72 %                                                                             %
73 %                                                                             %
74 +   P r o c e s s S c r i p t O p t i o n s                                   %
75 %                                                                             %
76 %                                                                             %
77 %                                                                             %
78 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
79 %
80 %  ProcessScriptOptions() reads options and processes options as they are
81 %  found in the given file, or pipeline.  The filename to open and read
82 %  options is given as the 'index' argument of the argument array given.
83 %
84 %  Other arguments following index may be read by special script options
85 %  as settings (strings), images, or as operations to be processed in various
86 %  ways.   How they are treated is up to the script being processed.
87 %
88 %  Note that a script not 'return' to the command line processing, nor can
89 %  they call (and return from) other scripts. At least not at this time.
90 %
91 %  There are no 'ProcessOptionFlags' control flags at this time.
92 %
93 %  The format of the ProcessScriptOptions method is:
94 %
95 %    void ProcessScriptOptions(MagickCLI *cli_wand,const char *filename,
96 %       int argc,char **argv,int index)
97 %
98 %  A description of each parameter follows:
99 %
100 %    o cli_wand: the main CLI Wand to use.
101 %
102 %    o filename: the filename of script to process
103 %
104 %    o argc: the number of elements in the argument vector. (optional)
105 %
106 %    o argv: A text array containing the command line arguments. (optional)
107 %
108 %    o index: offset of next argment in argv (script arguments) (optional)
109 %
110 */
ProcessScriptOptions(MagickCLI * cli_wand,const char * filename,int magick_unused (argc),char ** magick_unused (argv),int magick_unused (index))111 WandExport void ProcessScriptOptions(MagickCLI *cli_wand,const char *filename,
112   int magick_unused(argc),char **magick_unused(argv),int magick_unused(index))
113 {
114   ScriptTokenInfo
115     *token_info;
116 
117   CommandOptionFlags
118     option_type;
119 
120   int
121     count;
122 
123   char
124     *option,
125     *arg1,
126     *arg2;
127 
128   magick_unreferenced(argc);
129   magick_unreferenced(argv);
130   magick_unreferenced(index);
131   assert(filename != (char *) NULL ); /* at least one argument - script name */
132   assert(cli_wand != (MagickCLI *) NULL);
133   assert(cli_wand->signature == MagickWandSignature);
134   if (cli_wand->wand.debug != MagickFalse)
135     (void) LogMagickEvent(CommandEvent,GetMagickModule(),
136          "Processing script \"%s\"", filename);
137 
138   /* open file script or stream, and set up tokenizer */
139   token_info = AcquireScriptTokenInfo(filename);
140   if (token_info == (ScriptTokenInfo *) NULL) {
141     CLIWandExceptionFile(OptionFatalError,"UnableToOpenScript",filename);
142     return;
143   }
144 
145   /* define the error location string for use in exceptions
146      order of localtion format escapes: filename, line, column */
147   cli_wand->location="in \"%s\" at line %u,column %u";
148   if ( LocaleCompare("-", filename) == 0 )
149     cli_wand->filename="stdin";
150   else
151     cli_wand->filename=filename;
152 
153   /* Process Options from Script */
154   option = arg1 = arg2 = (char*) NULL;
155 DisableMSCWarning(4127)
156   while (1) {
157 RestoreMSCWarning
158 
159     { MagickBooleanType status = GetScriptToken(token_info);
160       cli_wand->line=token_info->token_line;
161       cli_wand->column=token_info->token_column;
162       if (status == MagickFalse)
163         break; /* error or end of options */
164     }
165 
166     do { /* use break to loop to exception handler and loop */
167 
168       /* save option details */
169       CloneString(&option,token_info->token);
170 
171       /* get option, its argument count, and option type */
172       cli_wand->command = GetCommandOptionInfo(option);
173       count=cli_wand->command->type;
174       option_type=(CommandOptionFlags) cli_wand->command->flags;
175 #if 0
176       (void) FormatLocaleFile(stderr, "Script: %u,%u: \"%s\" matched \"%s\"\n",
177           cli_wand->line, cli_wand->line, option, cli_wand->command->mnemonic );
178 #endif
179 
180       /* handle a undefined option - image read - always for "magick-script" */
181       if ( option_type == UndefinedOptionFlag ||
182            (option_type & NonMagickOptionFlag) != 0 ) {
183 #if MagickCommandDebug >= 3
184         (void) FormatLocaleFile(stderr, "Script %u,%u Non-Option: \"%s\"\n",
185                     cli_wand->line, cli_wand->line, option);
186 #endif
187         if (IsCommandOption(option) == MagickFalse) {
188           /* non-option -- treat as a image read */
189           cli_wand->command=(const OptionInfo *) NULL;
190           CLIOption(cli_wand,"-read",option);
191           break; /* next option */
192         }
193         CLIWandException(OptionFatalError,"UnrecognizedOption",option);
194         break; /* next option */
195       }
196 
197       if ( count >= 1 ) {
198         if (GetScriptToken(token_info) == MagickFalse)
199           CLIWandException(OptionFatalError,"MissingArgument",option);
200         CloneString(&arg1,token_info->token);
201       }
202       else
203         CloneString(&arg1,(char *) NULL);
204 
205       if ( count >= 2 ) {
206         if (GetScriptToken(token_info) == MagickFalse)
207           CLIWandExceptionBreak(OptionFatalError,"MissingArgument",option);
208         CloneString(&arg2,token_info->token);
209       }
210       else
211         CloneString(&arg2,(char *) NULL);
212 
213       /*
214         Process Options
215       */
216 #if MagickCommandDebug >= 3
217       (void) FormatLocaleFile(stderr,
218         "Script %u,%u Option: \"%s\"  Count: %d  Flags: %04x  Args: \"%s\" \"%s\"\n",
219             cli_wand->line,cli_wand->line,option,count,option_type,arg1,arg2);
220 #endif
221       /* Hard Deprecated Options, no code to execute - error */
222       if ( (option_type & DeprecateOptionFlag) != 0 ) {
223         CLIWandException(OptionError,"DeprecatedOptionNoCode",option);
224         break; /* next option */
225       }
226 
227       /* MagickCommandGenesis() options have no place in a magick script */
228       if ( (option_type & GenesisOptionFlag) != 0 ) {
229         CLIWandException(OptionError,"InvalidUseOfOption",option);
230         break; /* next option */
231       }
232 
233       /* handle any special 'script' options */
234       if ( (option_type & SpecialOptionFlag) != 0 ) {
235         if ( LocaleCompare(option,"-exit") == 0 ) {
236           goto loop_exit; /* break out of loop - return from script */
237         }
238         if ( LocaleCompare(option,"-script") == 0 ) {
239           /* FUTURE: call new script from this script - error for now */
240           CLIWandException(OptionError,"InvalidUseOfOption",option);
241           break; /* next option */
242         }
243         /* FUTURE: handle special script-argument options here */
244         /* handle any other special operators now */
245         CLIWandException(OptionError,"InvalidUseOfOption",option);
246         break; /* next option */
247       }
248 
249       /* Process non-specific Option */
250       CLIOption(cli_wand, option, arg1, arg2);
251       (void) fflush(stdout);
252       (void) fflush(stderr);
253 
254 DisableMSCWarning(4127)
255     } while (0); /* break block to next option */
256 RestoreMSCWarning
257 
258 #if MagickCommandDebug >= 5
259     fprintf(stderr, "Script Image Count = %ld\n",
260          GetImageListLength(cli_wand->wand.images) );
261 #endif
262     if (CLICatchException(cli_wand, MagickFalse) != MagickFalse)
263       break;  /* exit loop */
264   }
265 
266   /*
267      Loop exit - check for some tokenization error
268   */
269 loop_exit:
270 #if MagickCommandDebug >= 3
271   (void) FormatLocaleFile(stderr, "Script End: %d\n", token_info->status);
272 #endif
273   switch( token_info->status ) {
274     case TokenStatusOK:
275     case TokenStatusEOF:
276       if (cli_wand->image_list_stack != (Stack *) NULL)
277         CLIWandException(OptionError,"UnbalancedParenthesis", "(eof)");
278       else if (cli_wand->image_info_stack != (Stack *) NULL)
279         CLIWandException(OptionError,"UnbalancedBraces", "(eof)");
280       break;
281     case TokenStatusBadQuotes:
282       /* Ensure last token has a sane length for error report */
283       if( strlen(token_info->token) > INITAL_TOKEN_LENGTH-1 ) {
284         token_info->token[INITAL_TOKEN_LENGTH-4] = '.';
285         token_info->token[INITAL_TOKEN_LENGTH-3] = '.';
286         token_info->token[INITAL_TOKEN_LENGTH-2] = '.';
287         token_info->token[INITAL_TOKEN_LENGTH-1] = '\0';
288       }
289       CLIWandException(OptionFatalError,"ScriptUnbalancedQuotes",
290            token_info->token);
291       break;
292     case TokenStatusMemoryFailed:
293       CLIWandException(OptionFatalError,"ScriptTokenMemoryFailed","");
294       break;
295     case TokenStatusBinary:
296       CLIWandException(OptionFatalError,"ScriptIsBinary","");
297       break;
298   }
299   (void) fflush(stdout);
300   (void) fflush(stderr);
301   if (cli_wand->wand.debug != MagickFalse)
302     (void) LogMagickEvent(CommandEvent,GetMagickModule(),
303          "Script End \"%s\"", filename);
304 
305   /* Clean up */
306   token_info = DestroyScriptTokenInfo(token_info);
307 
308   CloneString(&option,(char *) NULL);
309   CloneString(&arg1,(char *) NULL);
310   CloneString(&arg2,(char *) NULL);
311 
312   return;
313 }
314 
315 /*
316 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
317 %                                                                             %
318 %                                                                             %
319 %                                                                             %
320 +  P r o c e s s C o m m a n d O p t i o n s                                  %
321 %                                                                             %
322 %                                                                             %
323 %                                                                             %
324 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
325 %
326 %  ProcessCommandOptions() reads and processes arguments in the given
327 %  command line argument array. The 'index' defines where in the array we
328 %  should begin processing
329 %
330 %  The 'process_flags' can be used to control and limit option processing.
331 %  For example, to only process one option, or how unknown and special options
332 %  are to be handled, and if the last argument in array is to be regarded as a
333 %  final image write argument (filename or special coder).
334 %
335 %  The format of the ProcessCommandOptions method is:
336 %
337 %    int ProcessCommandOptions(MagickCLI *cli_wand,int argc,char **argv,
338 %      int index)
339 %
340 %  A description of each parameter follows:
341 %
342 %    o cli_wand: the main CLI Wand to use.
343 %
344 %    o argc: the number of elements in the argument vector.
345 %
346 %    o argv: A text array containing the command line arguments.
347 %
348 %    o process_flags: What type of arguments will be processed, ignored
349 %                     or return errors.
350 %
351 %    o index: index in the argv array to start processing from
352 %
353 % The function returns the index ot the next option to be processed. This
354 % is really only releven if process_flags contains a ProcessOneOptionOnly
355 % flag.
356 %
357 */
ProcessCommandOptions(MagickCLI * cli_wand,int argc,char ** argv,int index)358 WandExport int ProcessCommandOptions(MagickCLI *cli_wand,int argc,char **argv,
359   int index)
360 {
361   const char
362     *option,
363     *arg1,
364     *arg2;
365 
366   int
367     i,
368     end,
369     count;
370 
371   CommandOptionFlags
372     option_type;
373 
374   assert(argc>=index); /* you may have no arguments left! */
375   assert(argv != (char **) NULL);
376   assert(argv[index] != (char *) NULL);
377   assert(argv[argc-1] != (char *) NULL);
378   assert(cli_wand != (MagickCLI *) NULL);
379   assert(cli_wand->signature == MagickWandSignature);
380 
381   /* define the error location string for use in exceptions
382      order of localtion format escapes: filename, line, column */
383   cli_wand->location="at %s arg %u";
384   cli_wand->filename="CLI";
385   cli_wand->line=index;  /* note first argument we will process */
386 
387   if (cli_wand->wand.debug != MagickFalse)
388     (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(),
389          "- Starting (\"%s\")", argv[index]);
390 
391   end = argc;
392   if ( (cli_wand->process_flags & ProcessImplictWrite) != 0 )
393     end--; /* the last arument is an implied write, do not process directly */
394 
395   for (i=index; i < end; i += count +1) {
396     /* Finished processing one option? */
397     if ( (cli_wand->process_flags & ProcessOneOptionOnly) != 0 && i != index )
398       return(i);
399 
400     do { /* use break to loop to exception handler and loop */
401 
402       option=argv[i];
403       cli_wand->line=i;  /* note the argument for this option */
404 
405       /* get option, its argument count, and option type */
406       cli_wand->command = GetCommandOptionInfo(argv[i]);
407       count=cli_wand->command->type;
408       option_type=(CommandOptionFlags) cli_wand->command->flags;
409 #if 0
410       (void) FormatLocaleFile(stderr, "CLI %d: \"%s\" matched \"%s\"\n",
411             i, argv[i], cli_wand->command->mnemonic );
412 #endif
413 
414       if ( option_type == UndefinedOptionFlag ||
415            (option_type & NonMagickOptionFlag) != 0 ) {
416 #if MagickCommandDebug >= 3
417         (void) FormatLocaleFile(stderr, "CLI arg %d Non-Option: \"%s\"\n",
418              i, option);
419 #endif
420         if (IsCommandOption(option) == MagickFalse) {
421           if ( (cli_wand->process_flags & ProcessImplictRead) != 0 ) {
422             /* non-option -- treat as a image read */
423             cli_wand->command=(const OptionInfo *) NULL;
424             CLIOption(cli_wand,"-read",option);
425             break; /* next option */
426           }
427         }
428         CLIWandException(OptionFatalError,"UnrecognizedOption",option);
429         break; /* next option */
430       }
431 
432       if ( ((option_type & SpecialOptionFlag) != 0 ) &&
433            ((cli_wand->process_flags & ProcessScriptOption) != 0) &&
434            (LocaleCompare(option,"-script") == 0) ) {
435         /* Call Script from CLI, with a filename as a zeroth argument.
436            NOTE: -script may need to use the 'implict write filename' argument
437            so it must be handled specially to prevent a 'missing argument' error.
438         */
439         if ( (i+count) >= argc )
440           CLIWandException(OptionFatalError,"MissingArgument",option);
441         ProcessScriptOptions(cli_wand,argv[i+1],argc,argv,i+count);
442         return(argc);  /* Script does not return to CLI -- Yet */
443                        /* FUTURE: when it does, their may be no write arg! */
444       }
445 
446       if ((i+count) >= end ) {
447         CLIWandException(OptionFatalError,"MissingArgument",option);
448         if ( CLICatchException(cli_wand, MagickFalse) != MagickFalse )
449           return(end);
450         break; /* next option - not that their is any! */
451       }
452 
453       arg1 = ( count >= 1 ) ? argv[i+1] : (char *) NULL;
454       arg2 = ( count >= 2 ) ? argv[i+2] : (char *) NULL;
455 
456       /*
457         Process Known Options
458       */
459 #if MagickCommandDebug >= 3
460       (void) FormatLocaleFile(stderr,
461         "CLI arg %u Option: \"%s\"  Count: %d  Flags: %04x  Args: \"%s\" \"%s\"\n",
462             i,option,count,option_type,arg1,arg2);
463 #endif
464       /* ignore 'genesis options' in command line args */
465       if ( (option_type & GenesisOptionFlag) != 0 )
466         break; /* next option */
467 
468       /* Handle any special options for CLI (-script handled above) */
469       if ( (option_type & SpecialOptionFlag) != 0 ) {
470         if ( (cli_wand->process_flags & ProcessExitOption) != 0
471              && LocaleCompare(option,"-exit") == 0 )
472           return(i+count);
473         break; /* next option */
474       }
475 
476       /* Process standard image option */
477       CLIOption(cli_wand, option, arg1, arg2);
478 
479 DisableMSCWarning(4127)
480     } while (0); /* break block to next option */
481 RestoreMSCWarning
482 
483 #if MagickCommandDebug >= 5
484     (void) FormatLocaleFile(stderr, "CLI-post Image Count = %ld\n",
485          (long) GetImageListLength(cli_wand->wand.images) );
486 #endif
487     if ( CLICatchException(cli_wand, MagickFalse) != MagickFalse )
488       return(i+count);
489   }
490   assert(i==end);
491 
492   if ( (cli_wand->process_flags & ProcessImplictWrite) == 0 )
493     return(end); /* no implied write -- just return to caller */
494 
495   assert(end==argc-1); /* end should not include last argument */
496 
497   /*
498      Implicit Write of images to final CLI argument
499   */
500   option=argv[i];
501   cli_wand->line=i;
502 
503   /* check that stacks are empty - or cause exception */
504   if (cli_wand->image_list_stack != (Stack *) NULL)
505     CLIWandException(OptionError,"UnbalancedParenthesis", "(end of cli)");
506   else if (cli_wand->image_info_stack != (Stack *) NULL)
507     CLIWandException(OptionError,"UnbalancedBraces", "(end of cli)");
508   if ( CLICatchException(cli_wand, MagickFalse) != MagickFalse )
509     return(argc);
510 
511 #if MagickCommandDebug >= 3
512   (void) FormatLocaleFile(stderr,"CLI arg %d Write File: \"%s\"\n",i,option);
513 #endif
514 
515   /* Valid 'do no write' replacement option (instead of "null:") */
516   if (LocaleCompare(option,"-exit") == 0 )
517     return(argc);  /* just exit, no image write */
518 
519   /* If filename looks like an option,
520      Or the common 'end of line' error of a single space.
521      -- produce an error */
522   if (IsCommandOption(option) != MagickFalse ||
523       (option[0] == ' ' && option[1] == '\0') ) {
524     CLIWandException(OptionError,"MissingOutputFilename",option);
525     return(argc);
526   }
527 
528   cli_wand->command=(const OptionInfo *) NULL;
529   CLIOption(cli_wand,"-write",option);
530   return(argc);
531 }
532 
533 /*
534 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
535 %                                                                             %
536 %                                                                             %
537 %                                                                             %
538 +   M a g i c k I m a g e C o m m a n d                                       %
539 %                                                                             %
540 %                                                                             %
541 %                                                                             %
542 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
543 %
544 %  MagickImageCommand() Handle special use CLI arguments and prepare a
545 %  CLI MagickCLI to process the command line or directly specified script.
546 %
547 %  This is essentualy interface function between the MagickCore library
548 %  initialization function MagickCommandGenesis(), and the option MagickCLI
549 %  processing functions  ProcessCommandOptions()  or  ProcessScriptOptions()
550 %
551 %  The format of the MagickImageCommand method is:
552 %
553 %      MagickBooleanType MagickImageCommand(ImageInfo *image_info,int argc,
554 %        char **argv,char **metadata,ExceptionInfo *exception)
555 %
556 %  A description of each parameter follows:
557 %
558 %    o image_info: the starting image_info structure
559 %      (for compatibilty with MagickCommandGenisis())
560 %
561 %    o argc: the number of elements in the argument vector.
562 %
563 %    o argv: A text array containing the command line arguments.
564 %
565 %    o metadata: any metadata (for VBS) is returned here.
566 %      (for compatibilty with MagickCommandGenisis())
567 %
568 %    o exception: return any errors or warnings in this structure.
569 %
570 */
571 
MagickUsage(MagickBooleanType verbose)572 static void MagickUsage(MagickBooleanType verbose)
573 {
574   const char
575     *name;
576 
577   size_t
578     len;
579 
580   name=GetClientName();
581   len=strlen(name);
582 
583   if (len>=7 && LocaleCompare("convert",name+len-7) == 0) {
584     /* convert usage */
585     (void) FormatLocaleFile(stdout,
586        "Usage: %s [ {option} | {image} ... ] {output_image}\n",name);
587     (void) FormatLocaleFile(stdout,
588        "       %s -help | -version | -usage | -list {option}\n\n",name);
589     return;
590   }
591   else if (len>=6 && LocaleCompare("script",name+len-6) == 0) {
592     /* magick-script usage */
593     (void) FormatLocaleFile(stdout,
594       "Usage: %s {filename} [ {script_args} ... ]\n",name);
595   }
596   else {
597     /* magick usage */
598     (void) FormatLocaleFile(stdout,
599        "Usage: %s tool [ {option} | {image} ... ] {output_image}\n",name);
600     (void) FormatLocaleFile(stdout,
601        "Usage: %s [ {option} | {image} ... ] {output_image}\n",name);
602     (void) FormatLocaleFile(stdout,
603        "       %s [ {option} | {image} ... ] -script {filename} [ {script_args} ...]\n",
604        name);
605   }
606   (void) FormatLocaleFile(stdout,
607     "       %s -help | -version | -usage | -list {option}\n\n",name);
608 
609   if (verbose == MagickFalse)
610     return;
611 
612   (void) FormatLocaleFile(stdout,"%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s\n",
613     "All options are performed in a strict 'as you see them' order\n",
614     "You must read-in images before you can operate on them.\n",
615     "\n",
616     "Magick Script files can use any of the following forms...\n",
617     "     #!/path/to/magick -script\n",
618     "or\n",
619     "     #!/bin/sh\n",
620     "     :; exec magick -script \"$0\" \"$@\"; exit 10\n",
621     "     # Magick script from here...\n",
622     "or\n",
623     "     #!/usr/bin/env  magick-script\n",
624     "The latter two forms do not require the path to the command hard coded.\n",
625     "Note: \"magick-script\" needs to be linked to the \"magick\" command.\n",
626     "\n",
627     "For more information on usage, options, examples, and techniques\n",
628     "see the ImageMagick website at    ", MagickAuthoritativeURL);
629 
630   return;
631 }
632 
633 /*
634    Concatanate given file arguments to the given output argument.
635    Used for a special -concatenate option used for specific 'delegates'.
636    The option is not formally documented.
637 
638       magick -concatenate files... output
639 
640    This is much like the UNIX "cat" command, but for both UNIX and Windows,
641    however the last argument provides the output filename.
642 */
ConcatenateImages(int argc,char ** argv,ExceptionInfo * exception)643 static MagickBooleanType ConcatenateImages(int argc,char **argv,
644   ExceptionInfo *exception )
645 {
646   FILE
647     *input,
648     *output;
649 
650   MagickBooleanType
651     status;
652 
653   int
654     c;
655 
656   ssize_t
657     i;
658 
659   if (ExpandFilenames(&argc,&argv) == MagickFalse)
660     ThrowFileException(exception,ResourceLimitError,"MemoryAllocationFailed",
661       GetExceptionMessage(errno));
662   output=fopen_utf8(argv[argc-1],"wb");
663   if (output == (FILE *) NULL)
664     {
665       ThrowFileException(exception,FileOpenError,"UnableToOpenFile",
666         argv[argc-1]);
667       return(MagickFalse);
668     }
669   status=MagickTrue;
670   for (i=2; i < (ssize_t) (argc-1); i++)
671   {
672     input=fopen_utf8(argv[i],"rb");
673     if (input == (FILE *) NULL)
674       {
675         ThrowFileException(exception,FileOpenError,"UnableToOpenFile",argv[i]);
676         continue;
677       }
678     for (c=fgetc(input); c != EOF; c=fgetc(input))
679       if (fputc((char) c,output) != c)
680         status=MagickFalse;
681     (void) fclose(input);
682     (void) remove_utf8(argv[i]);
683   }
684   (void) fclose(output);
685   return(status);
686 }
687 
MagickImageCommand(ImageInfo * image_info,int argc,char ** argv,char ** metadata,ExceptionInfo * exception)688 WandExport MagickBooleanType MagickImageCommand(ImageInfo *image_info,int argc,
689   char **argv,char **metadata,ExceptionInfo *exception)
690 {
691   MagickCLI
692     *cli_wand;
693 
694   size_t
695     len;
696 
697   assert(image_info != (ImageInfo *) NULL);
698 
699   /* For specific OS command line requirements */
700   ReadCommandlLine(argc,&argv);
701 
702   /* Initialize special "CLI Wand" to hold images and settings (empty) */
703   cli_wand=AcquireMagickCLI(image_info,exception);
704   cli_wand->location="Initializing";
705   cli_wand->filename=argv[0];
706   cli_wand->line=1;
707 
708   if (cli_wand->wand.debug != MagickFalse)
709     (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(),
710          "\"%s\"",argv[0]);
711 
712 
713   GetPathComponent(argv[0],TailPath,cli_wand->wand.name);
714   SetClientName(cli_wand->wand.name);
715   ConcatenateMagickString(cli_wand->wand.name,"-CLI",MagickPathExtent);
716 
717   len=strlen(argv[0]);  /* precaution */
718 
719   /* "convert" command - give a "deprecated" warning" */
720   if (len>=7 && LocaleCompare("convert",argv[0]+len-7) == 0) {
721     cli_wand->process_flags = ConvertCommandOptionFlags;
722     (void) FormatLocaleFile(stderr,"WARNING: %s\n",
723          "The convert command is deprecated in IMv7, use \"magick\"\n");
724   }
725 
726   /* Special Case:  If command name ends with "script" implied "-script" */
727   if (len>=6 && LocaleCompare("script",argv[0]+len-6) == 0) {
728     if (argc >= 2 && (  (*(argv[1]) != '-') || (strlen(argv[1]) == 1) )) {
729       GetPathComponent(argv[1],TailPath,cli_wand->wand.name);
730       ProcessScriptOptions(cli_wand,argv[1],argc,argv,2);
731       goto Magick_Command_Cleanup;
732     }
733   }
734 
735   /* Special Case: Version Information and Abort */
736   if (argc == 2) {
737     if ((LocaleCompare("-version",argv[1]) == 0)   || /* GNU standard option */
738         (LocaleCompare("--version",argv[1]) == 0) ) { /* just version */
739       CLIOption(cli_wand, "-version");
740       goto Magick_Command_Exit;
741     }
742     if ((LocaleCompare("-help",argv[1]) == 0)   || /* GNU standard option */
743         (LocaleCompare("--help",argv[1]) == 0) ) { /* just a brief summary */
744       if (cli_wand->wand.debug != MagickFalse)
745         (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(),
746             "- Special Option \"%s\"", argv[1]);
747       MagickUsage(MagickFalse);
748       goto Magick_Command_Exit;
749     }
750     if (LocaleCompare("-usage",argv[1]) == 0) {   /* both version & usage */
751       if (cli_wand->wand.debug != MagickFalse)
752         (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(),
753             "- Special Option \"%s\"", argv[1]);
754       CLIOption(cli_wand, "-version" );
755       MagickUsage(MagickTrue);
756       goto Magick_Command_Exit;
757     }
758   }
759 
760   /* not enough arguments -- including -help */
761   if (argc < 3) {
762     (void) FormatLocaleFile(stderr,
763        "Error: Invalid argument or not enough arguments\n\n");
764     MagickUsage(MagickFalse);
765     goto Magick_Command_Exit;
766   }
767 
768   /* Special "concatenate option (hidden) for delegate usage */
769   if (LocaleCompare("-concatenate",argv[1]) == 0) {
770     if (cli_wand->wand.debug != MagickFalse)
771         (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(),
772             "- Special Option \"%s\"", argv[1]);
773     ConcatenateImages(argc,argv,exception);
774     goto Magick_Command_Exit;
775   }
776 
777   /* List Information and Abort */
778   if (argc == 3 && LocaleCompare("-list",argv[1]) == 0) {
779     CLIOption(cli_wand, argv[1], argv[2]);
780     goto Magick_Command_Exit;
781   }
782 
783   /* ------------- */
784   /* The Main Call */
785 
786   if (LocaleCompare("-script",argv[1]) == 0) {
787     /* Start processing directly from script, no pre-script options
788        Replace wand command name with script name
789        First argument in the argv array is the script name to read.
790     */
791     GetPathComponent(argv[2],TailPath,cli_wand->wand.name);
792     ProcessScriptOptions(cli_wand,argv[2],argc,argv,3);
793   }
794   else {
795     /* Normal Command Line, assumes output file as last option */
796     ProcessCommandOptions(cli_wand,argc,argv,1);
797   }
798   /* ------------- */
799 
800 Magick_Command_Cleanup:
801   cli_wand->location="Cleanup";
802   cli_wand->filename=argv[0];
803   if (cli_wand->wand.debug != MagickFalse)
804     (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(),
805          "\"%s\"",argv[0]);
806 
807   /* recover original image_info and clean up stacks
808      FUTURE: "-reset stacks" option  */
809   while ((cli_wand->image_list_stack != (Stack *) NULL) &&
810          (cli_wand->image_list_stack->next != (Stack *) NULL))
811     CLIOption(cli_wand,")");
812   while ((cli_wand->image_info_stack != (Stack *) NULL) &&
813          (cli_wand->image_info_stack->next != (Stack *) NULL))
814     CLIOption(cli_wand,"}");
815 
816   /* assert we have recovered the original structures */
817   assert(cli_wand->wand.image_info == image_info);
818   assert(cli_wand->wand.exception == exception);
819 
820   /* Handle metadata for ImageMagickObject COM object for Windows VBS */
821   if ((cli_wand->wand.images != (Image *) NULL) &&
822       (metadata != (char **) NULL))
823     {
824       const char
825         *format;
826 
827       char
828         *text;
829 
830       format="%w,%h,%m";  /* Get this from image_info Option splaytree */
831       text=InterpretImageProperties(image_info,cli_wand->wand.images,format,
832         exception);
833       if (text == (char *) NULL)
834         ThrowMagickException(exception,GetMagickModule(),ResourceLimitError,
835           "MemoryAllocationFailed","`%s'", GetExceptionMessage(errno));
836       else
837         {
838           (void) ConcatenateString(&(*metadata),text);
839           text=DestroyString(text);
840         }
841     }
842 
843 Magick_Command_Exit:
844   cli_wand->location="Exiting";
845   cli_wand->filename=argv[0];
846   if (cli_wand->wand.debug != MagickFalse)
847     (void) CLILogEvent(cli_wand,CommandEvent,GetMagickModule(),
848          "\"%s\"",argv[0]);
849 
850   /* Destroy the special CLI Wand */
851   cli_wand->wand.image_info = (ImageInfo *) NULL; /* not these */
852   cli_wand->wand.exception = (ExceptionInfo *) NULL;
853   cli_wand=DestroyMagickCLI(cli_wand);
854 
855   return(exception->severity < ErrorException ? MagickTrue : MagickFalse);
856 }
857