MagickWand  7.1.2-29
Convert, Edit, Or Compose Bitmap Images
script-token.c
1 /*
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 % %
4 % %
5 % SSS CCC RRRR III PPPP TTTTT TTTTT OOO K K EEEE N N %
6 % S C R R I P P T T O O K K E NN N %
7 % SSS C RRRR I PPPP T T O O KK EEE N N N %
8 % S C R R I P T T O O K K E N NN %
9 % SSSS CCC R RR III P T T OOO K K EEEE N N %
10 % %
11 % Tokenize Magick Script into Options %
12 % %
13 % Dragon Computing %
14 % Anthony Thyssen %
15 % January 2012 %
16 % %
17 % %
18 % Copyright @ 1999 ImageMagick Studio LLC, a non-profit organization %
19 % dedicated to making software imaging solutions freely available. %
20 % %
21 % You may not use this file except in compliance with the License. You may %
22 % obtain a copy of the License at %
23 % %
24 % https://imagemagick.org/license/ %
25 % %
26 % Unless required by applicable law or agreed to in writing, software %
27 % distributed under the License is distributed on an "AS IS" BASIS, %
28 % WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. %
29 % See the License for the specific language governing permissions and %
30 % limitations under the License. %
31 % %
32 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
33 %
34 % Read a stream of characters and return tokens one at a time.
35 %
36 % The input stream is divided into individual 'tokens' (representing 'words'
37 % or 'options'), in a way that is as close to a UNIX shell, as is feasible.
38 % Only shell variable, and command substitutions will not be performed.
39 % Tokens can be any length.
40 %
41 % The main function call is GetScriptToken() (see below) which returns one
42 % and only one token at a time. The other functions provide support to this
43 % function, opening scripts, and setting up the required structures.
44 %
45 % More specifically...
46 %
47 % Tokens are white space separated, and may be quoted, or even partially
48 % quoted by either single or double quotes, or the use of backslashes,
49 % or any mix of the three.
50 %
51 % For example: This\ is' a 'single" token"
52 %
53 % A token is returned immediately the end of token is found. That is as soon
54 % as a unquoted white-space or EOF condition has been found. That is to say
55 % the file stream is parsed purely character-by-character, regardless any
56 % buffering constraints set by the system. It is not parsed line-by-line.
57 %
58 % The function will return 'MagickTrue' if a valid token was found, while
59 % the token status will be set accordingly to 'OK' or 'EOF', according to
60 % the cause of the end of token. The token may be an empty string if the
61 % input was a quoted empty string. Other error conditions return a value of
62 % MagickFalse, indicating any token found but was incomplete due to some
63 % error condition.
64 %
65 % Single quotes will preserve all characters including backslashes. Double
66 % quotes will also preserve backslashes unless escaping a double quote,
67 % or another backslashes. Other shell meta-characters are not treated as
68 % special by this tokenizer.
69 %
70 % For example Quoting the quote chars:
71 % \' "'" \" '"' "\"" \\ '\' "\\"
72 %
73 % Outside quotes, backslash characters will make spaces, tabs and quotes part
74 % of a token returned. However a backslash at the end of a line (and outside
75 % quotes) will cause the newline to be completely ignored (as per the shell
76 % line continuation).
77 %
78 % Comments start with a '#' character at the start of a new token, will be
79 % completely ignored upto the end of line, regardless of any backslash at the
80 % end of the line. You can escape a comment '#', using quotes or backslashes
81 % just as you can in a shell.
82 %
83 % The parser will accept both newlines, returns, or return-newlines to mark
84 % the EOL. Though this is technically breaking (or perhaps adding to) the
85 % 'BASH' syntax that is being followed.
86 %
87 %
88 % UNIX script Launcher...
89 %
90 % The use of '#' comments allow normal UNIX 'scripting' to be used to call on
91 % the "magick" command to parse the tokens from a file
92 %
93 % #!/path/to/command/magick -script
94 %
95 %
96 % UNIX 'env' command launcher...
97 %
98 % If "magick" is renamed "magick-script" you can use a 'env' UNIX launcher
99 %
100 % #!/usr/bin/env magick-script
101 %
102 %
103 % Shell script launcher...
104 %
105 % As a special case a ':' at the start of a line is also treated as a comment
106 % This allows a magick script to ignore a line that can be parsed by the shell
107 % and not by the magick script (tokenizer). This allows for an alternative
108 % script 'launcher' to be used for magick scripts.
109 %
110 % #!/bin/sh
111 % :; exec magick -script "$0" "$@"; exit 10
112 % #
113 % # The rest of the file is magick script
114 % -read label:"This is a Magick Script!"
115 % -write show: -exit
116 %
117 % Or with some shell pre/post processing...
118 %
119 % #!/bin/sh
120 % :; echo "This part is run in the shell, but ignored by Magick"
121 % :; magick -script "$0" "$@"
122 % :; echo "This is run after the "magick" script is finished!"
123 % :; exit 10
124 % #
125 % # The rest of the file is magick script
126 % -read label:"This is a Magick Script!"
127 % -write show: -exit
128 %
129 %
130 % DOS script launcher...
131 %
132 % Similarly any '@' at the start of the line (outside of quotes) will also be
133 % treated as comment. This allow you to create a DOS script launcher, to
134 % allow a ".bat" DOS scripts to run as "magick" scripts instead.
135 %
136 % @echo This line is DOS executed but ignored by Magick
137 % @magick -script %~dpnx0 %*
138 % @echo This line is processed after the Magick script is finished
139 % @GOTO :EOF
140 % #
141 % # The rest of the file is magick script
142 % -read label:"This is a Magick Script!"
143 % -write show: -exit
144 %
145 % But this can also be used as a shell script launcher as well!
146 % Though is more restrictive and less free-form than using ':'.
147 %
148 % #!/bin/sh
149 % @() { exec magick -script "$@"; }
150 % @ "$0" "$@"; exit
151 % #
152 % # The rest of the file is magick script
153 % -read label:"This is a Magick Script!"
154 % -write show: -exit
155 %
156 % Or even like this...
157 %
158 % #!/bin/sh
159 % @() { }
160 % @; exec magick -script "$0" "$@"; exit
161 % #
162 % # The rest of the file is magick script
163 % -read label:"This is a Magick Script!"
164 % -write show: -exit
165 %
166 */
167 ␌
168 /*
169  Include declarations.
170 
171  NOTE: Do not include if being compiled into the "test/script-token-test.c"
172  module, for low level token testing.
173 */
174 #ifndef SCRIPT_TOKEN_TESTING
175 # include "MagickWand/studio.h"
176 # include "MagickWand/MagickWand.h"
177 # include "MagickWand/script-token.h"
178 # include "MagickCore/exception-private.h"
179 # include "MagickCore/policy.h"
180 # include "MagickCore/policy-private.h"
181 # include "MagickCore/string-private.h"
182 # include "MagickCore/utility-private.h"
183 #endif
184 ␌
185 /*
186 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
187 % %
188 % %
189 % %
190 % A c q u i r e S c r i p t T o k e n I n f o %
191 % %
192 % %
193 % %
194 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
195 %
196 % AcquireScriptTokenInfo() allocated, initializes and opens the given
197 % file stream from which tokens are to be extracted.
198 %
199 % The format of the AcquireScriptTokenInfo method is:
200 %
201 % ScriptTokenInfo *AcquireScriptTokenInfo(char *filename)
202 %
203 % A description of each parameter follows:
204 %
205 % o filename the filename to open ("-" means stdin)
206 %
207 */
208 WandExport ScriptTokenInfo *AcquireScriptTokenInfo(const char *filename)
209 {
211  *token_info;
212 
213  if (IsPathAuthorized(ReadPolicyRights,filename) == MagickFalse)
214  return((ScriptTokenInfo *) NULL);
215  token_info=(ScriptTokenInfo *) AcquireMagickMemory(sizeof(*token_info));
216  if (token_info == (ScriptTokenInfo *) NULL)
217  return(token_info);
218  (void) memset(token_info,0,sizeof(*token_info));
219 
220  token_info->opened=MagickFalse;
221  if ( LocaleCompare(filename,"-") == 0 ) {
222  token_info->stream=stdin;
223  token_info->opened=MagickFalse;
224  }
225  else if (LocaleNCompare(filename,"fd:",3) == 0 ) {
226  token_info->stream=fdopen(StringToLong(filename+3),"r");
227  token_info->opened=MagickFalse;
228  }
229  else {
230  token_info->stream=fopen_utf8(filename, "r");
231  }
232  if ( token_info->stream == (FILE *) NULL ) {
233  token_info=(ScriptTokenInfo *) RelinquishMagickMemory(token_info);
234  return(token_info);
235  }
236 
237  token_info->curr_line=1;
238  token_info->length=INITAL_TOKEN_LENGTH;
239  token_info->token=(char *) AcquireQuantumMemory(1,token_info->length);
240 
241  token_info->status=(token_info->token != (char *) NULL)
242  ? TokenStatusOK : TokenStatusMemoryFailed;
243  token_info->signature=MagickWandSignature;
244 
245  return token_info;
246 }
247 ␌
248 /*
249 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
250 % %
251 % %
252 % %
253 % D e s t r o y S c r i p t T o k e n I n f o %
254 % %
255 % %
256 % %
257 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
258 %
259 % DestroyScriptTokenInfo() allocated, initializes and opens the given
260 % file stream from which tokens are to be extracted.
261 %
262 % The format of the DestroyScriptTokenInfo method is:
263 %
264 % ScriptTokenInfo *DestroyScriptTokenInfo(ScriptTokenInfo *token_info)
265 %
266 % A description of each parameter follows:
267 %
268 % o token_info The ScriptTokenInfo structure to be destroyed
269 %
270 */
271 WandExport ScriptTokenInfo * DestroyScriptTokenInfo(ScriptTokenInfo *token_info)
272 {
273  assert(token_info != (ScriptTokenInfo *) NULL);
274  assert(token_info->signature == MagickWandSignature);
275 
276  if ( token_info->opened != MagickFalse )
277  (void) fclose(token_info->stream);
278 
279  if (token_info->token != (char *) NULL )
280  token_info->token=(char *) RelinquishMagickMemory(token_info->token);
281  token_info=(ScriptTokenInfo *) RelinquishMagickMemory(token_info);
282  return(token_info);
283 }
284 ␌
285 /*
286 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
287 % %
288 % %
289 % %
290 % G e t S c r i p t T o k e n %
291 % %
292 % %
293 % %
294 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
295 %
296 % GetScriptToken() a fairly general, finite state token parser. That returns
297 % tokens one at a time, as soon as possible.
298 %
299 %
300 % The format of the GetScriptToken method is:
301 %
302 % MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
303 %
304 % A description of each parameter follows:
305 %
306 % o token_info pointer to a structure holding token details
307 %
308 */
309 /* States of the parser */
310 #define IN_WHITE 0
311 #define IN_TOKEN 1
312 #define IN_QUOTE 2
313 #define IN_COMMENT 3
314 
315 /* Macro to read character from stream
316 
317  This also keeps track of the line and column counts.
318  The EOL is defined as either '\r\n', or '\r', or '\n'.
319  A '\r' on its own is converted into a '\n' to correctly handle
320  raw input, typically due to 'copy-n-paste' of text files.
321  But a '\r\n' sequence is left ASIS for string handling
322 */
323 #define GetChar(c) \
324 { \
325  c=fgetc(token_info->stream); \
326  token_info->curr_column++; \
327  if ( c == '\r' ) { \
328  c=fgetc(token_info->stream); \
329  ungetc(c,token_info->stream); \
330  c = (c!='\n')?'\n':'\r'; \
331  } \
332  if ( c == '\n' ) \
333  token_info->curr_line++, token_info->curr_column=0; \
334  if (c == EOF ) \
335  break; \
336  if ( (c>='\0' && c<'\a') || (c>'\r' && c<' ' && c!='\033') ) { \
337  token_info->status=TokenStatusBinary; \
338  break; \
339  } \
340 }
341 /* macro to collect the token characters */
342 #define SaveChar(c) \
343 { \
344  if ((size_t) offset >= (token_info->length-1)) { \
345  if (token_info == (ScriptTokenInfo *) NULL) \
346  break; \
347  if ( token_info->length >= MagickPathExtent ) \
348  token_info->length += MagickPathExtent; \
349  else \
350  token_info->length *= 4; \
351  token_info->token=(char *) ResizeQuantumMemory(token_info->token, \
352  token_info->length,sizeof(*token_info->token)); \
353  if ( token_info->token == (char *) NULL ) { \
354  token_info->status=TokenStatusMemoryFailed; \
355  break; \
356  } \
357  } \
358  if ( token_info->token == (char *) NULL ) \
359  token_info->status=TokenStatusMemoryFailed; \
360  else \
361  token_info->token[offset++]=(char) (c); \
362 }
363 
364 WandExport MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
365 {
366  int
367  quote,
368  c;
369 
370  int
371  state;
372 
373  ssize_t
374  offset;
375 
376  /* EOF - no more tokens! */
377  if (token_info == (ScriptTokenInfo *) NULL)
378  return(MagickFalse);
379  if (token_info->status != TokenStatusOK)
380  {
381  token_info->token[0]='\0';
382  return(MagickFalse);
383  }
384  state=IN_WHITE;
385  quote='\0';
386  offset=0;
387 DisableMSCWarning(4127)
388  while(1)
389 RestoreMSCWarning
390  {
391  /* get character */
392  GetChar(c);
393 
394  /* hash comment handling */
395  if ( state == IN_COMMENT ) {
396  if ( c == '\n' )
397  state=IN_WHITE;
398  continue;
399  }
400  /* comment lines start with '#' anywhere, or ':' or '@' at start of line */
401  if ( state == IN_WHITE )
402  if ( ( c == '#' ) ||
403  ( token_info->curr_column==1 && (c == ':' || c == '@' ) ) )
404  state=IN_COMMENT;
405  /* whitespace token separator character */
406  if (strchr(" \n\r\t",c) != (char *) NULL) {
407  switch (state) {
408  case IN_TOKEN:
409  token_info->token[offset]='\0';
410  return(MagickTrue);
411  case IN_QUOTE:
412  SaveChar(c);
413  break;
414  }
415  continue;
416  }
417  /* quote character */
418  if ( c=='\'' || c =='"' ) {
419  switch (state) {
420  case IN_WHITE:
421  token_info->token_line=token_info->curr_line;
422  token_info->token_column=token_info->curr_column;
423  magick_fallthrough;
424  case IN_TOKEN:
425  state=IN_QUOTE;
426  quote=c;
427  break;
428  case IN_QUOTE:
429  if (c == quote)
430  {
431  state=IN_TOKEN;
432  quote='\0';
433  }
434  else
435  SaveChar(c);
436  break;
437  }
438  continue;
439  }
440  /* escape char (preserve in quotes - unless escaping the same quote) */
441  if (c == '\\')
442  {
443  if ( state==IN_QUOTE && quote == '\'' ) {
444  SaveChar('\\');
445  continue;
446  }
447  GetChar(c);
448  if (c == '\n')
449  switch (state) {
450  case IN_COMMENT:
451  state=IN_WHITE; /* end comment */
452  magick_fallthrough;
453  case IN_QUOTE:
454  if (quote != '"')
455  break; /* in double quotes only */
456  magick_fallthrough;
457  case IN_WHITE:
458  case IN_TOKEN:
459  continue; /* line continuation - remove line feed */
460  }
461  switch (state) {
462  case IN_WHITE:
463  token_info->token_line=token_info->curr_line;
464  token_info->token_column=token_info->curr_column;
465  state=IN_TOKEN;
466  break;
467  case IN_QUOTE:
468  if (c != quote && c != '\\')
469  SaveChar('\\');
470  break;
471  }
472  SaveChar(c);
473  continue;
474  }
475  /* ordinary character */
476  switch (state) {
477  case IN_WHITE:
478  token_info->token_line=token_info->curr_line;
479  token_info->token_column=token_info->curr_column;
480  state=IN_TOKEN;
481  magick_fallthrough;
482  case IN_TOKEN:
483  case IN_QUOTE:
484  SaveChar(c);
485  break;
486  case IN_COMMENT:
487  break;
488  }
489  }
490  /* input stream has EOF or produced a fatal error */
491  token_info->token[offset]='\0';
492  if ( token_info->status != TokenStatusOK )
493  return(MagickFalse); /* fatal condition - no valid token */
494  token_info->status = TokenStatusEOF;
495  if ( state == IN_QUOTE)
496  token_info->status = TokenStatusBadQuotes;
497  if ( state == IN_TOKEN)
498  return(MagickTrue); /* token with EOF at end - no problem */
499  return(MagickFalse); /* in white space or in quotes - invalid token */
500 }