MagickWand 7.1.2-32
Convert, Edit, Or Compose Bitmap Images
Loading...
Searching...
No Matches
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*/
208WandExport 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),"rb");
227 token_info->opened=MagickFalse;
228 }
229 else {
230 int fd = open_utf8(filename,O_RDONLY | O_CLOEXEC | O_NOFOLLOW,0);
231 if (fd != -1)
232 token_info->stream=fdopen(fd,"r");
233 if (token_info->stream != (FILE *) NULL)
234 token_info->opened=MagickTrue;
235 else
236 fd=close_utf8(fd)-1;
237 }
238 if ((token_info->stream != (FILE *) NULL) &&
239 (IsPathAuthorized(ReadPolicyRights,filename) != MagickFalse))
240 token_info->opened=MagickTrue;
241 else
242 {
243 token_info=(ScriptTokenInfo *) RelinquishMagickMemory(token_info);
244 return(token_info);
245 }
246
247 token_info->curr_line=1;
248 token_info->length=INITAL_TOKEN_LENGTH;
249 token_info->token=(char *) AcquireQuantumMemory(1,token_info->length);
250
251 token_info->status=(token_info->token != (char *) NULL)
252 ? TokenStatusOK : TokenStatusMemoryFailed;
253 token_info->signature=MagickWandSignature;
254
255 return token_info;
256}
257
258/*
259%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
260% %
261% %
262% %
263% D e s t r o y S c r i p t T o k e n I n f o %
264% %
265% %
266% %
267%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
268%
269% DestroyScriptTokenInfo() allocated, initializes and opens the given
270% file stream from which tokens are to be extracted.
271%
272% The format of the DestroyScriptTokenInfo method is:
273%
274% ScriptTokenInfo *DestroyScriptTokenInfo(ScriptTokenInfo *token_info)
275%
276% A description of each parameter follows:
277%
278% o token_info The ScriptTokenInfo structure to be destroyed
279%
280*/
281WandExport ScriptTokenInfo * DestroyScriptTokenInfo(ScriptTokenInfo *token_info)
282{
283 assert(token_info != (ScriptTokenInfo *) NULL);
284 assert(token_info->signature == MagickWandSignature);
285
286 if ( token_info->opened != MagickFalse )
287 (void) fclose(token_info->stream);
288
289 if (token_info->token != (char *) NULL )
290 token_info->token=(char *) RelinquishMagickMemory(token_info->token);
291 token_info=(ScriptTokenInfo *) RelinquishMagickMemory(token_info);
292 return(token_info);
293}
294
295/*
296%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
297% %
298% %
299% %
300% G e t S c r i p t T o k e n %
301% %
302% %
303% %
304%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
305%
306% GetScriptToken() a fairly general, finite state token parser. That returns
307% tokens one at a time, as soon as possible.
308%
309%
310% The format of the GetScriptToken method is:
311%
312% MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
313%
314% A description of each parameter follows:
315%
316% o token_info pointer to a structure holding token details
317%
318*/
319/* States of the parser */
320#define IN_WHITE 0
321#define IN_TOKEN 1
322#define IN_QUOTE 2
323#define IN_COMMENT 3
324
325/* Macro to read character from stream
326
327 This also keeps track of the line and column counts.
328 The EOL is defined as either '\r\n', or '\r', or '\n'.
329 A '\r' on its own is converted into a '\n' to correctly handle
330 raw input, typically due to 'copy-n-paste' of text files.
331 But a '\r\n' sequence is left ASIS for string handling
332*/
333#define GetChar(c) \
334{ \
335 c=fgetc(token_info->stream); \
336 token_info->curr_column++; \
337 if ( c == '\r' ) { \
338 c=fgetc(token_info->stream); \
339 ungetc(c,token_info->stream); \
340 c = (c!='\n')?'\n':'\r'; \
341 } \
342 if ( c == '\n' ) \
343 token_info->curr_line++, token_info->curr_column=0; \
344 if (c == EOF ) \
345 break; \
346 if ( (c>='\0' && c<'\a') || (c>'\r' && c<' ' && c!='\033') ) { \
347 token_info->status=TokenStatusBinary; \
348 break; \
349 } \
350}
351/* macro to collect the token characters */
352#define SaveChar(c) \
353{ \
354 if ((size_t) offset >= (token_info->length-1)) { \
355 if (token_info == (ScriptTokenInfo *) NULL) \
356 break; \
357 if ( token_info->length >= MagickPathExtent ) \
358 token_info->length += MagickPathExtent; \
359 else \
360 token_info->length *= 4; \
361 token_info->token=(char *) ResizeQuantumMemory(token_info->token, \
362 token_info->length,sizeof(*token_info->token)); \
363 if ( token_info->token == (char *) NULL ) { \
364 token_info->status=TokenStatusMemoryFailed; \
365 break; \
366 } \
367 } \
368 if ( token_info->token == (char *) NULL ) \
369 token_info->status=TokenStatusMemoryFailed; \
370 else \
371 token_info->token[offset++]=(char) (c); \
372}
373
374WandExport MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
375{
376 int
377 quote,
378 c;
379
380 int
381 state;
382
383 ssize_t
384 offset;
385
386 /* EOF - no more tokens! */
387 if (token_info == (ScriptTokenInfo *) NULL)
388 return(MagickFalse);
389 if (token_info->status != TokenStatusOK)
390 {
391 token_info->token[0]='\0';
392 return(MagickFalse);
393 }
394 state=IN_WHITE;
395 quote='\0';
396 offset=0;
397DisableMSCWarning(4127)
398 while(1)
399RestoreMSCWarning
400 {
401 /* get character */
402 GetChar(c);
403
404 /* hash comment handling */
405 if ( state == IN_COMMENT ) {
406 if ( c == '\n' )
407 state=IN_WHITE;
408 continue;
409 }
410 /* comment lines start with '#' anywhere, or ':' or '@' at start of line */
411 if ( state == IN_WHITE )
412 if ( ( c == '#' ) ||
413 ( token_info->curr_column==1 && (c == ':' || c == '@' ) ) )
414 state=IN_COMMENT;
415 /* whitespace token separator character */
416 if (strchr(" \n\r\t",c) != (char *) NULL) {
417 switch (state) {
418 case IN_TOKEN:
419 token_info->token[offset]='\0';
420 return(MagickTrue);
421 case IN_QUOTE:
422 SaveChar(c);
423 break;
424 }
425 continue;
426 }
427 /* quote character */
428 if ( c=='\'' || c =='"' ) {
429 switch (state) {
430 case IN_WHITE:
431 token_info->token_line=token_info->curr_line;
432 token_info->token_column=token_info->curr_column;
433 magick_fallthrough;
434 case IN_TOKEN:
435 state=IN_QUOTE;
436 quote=c;
437 break;
438 case IN_QUOTE:
439 if (c == quote)
440 {
441 state=IN_TOKEN;
442 quote='\0';
443 }
444 else
445 SaveChar(c);
446 break;
447 }
448 continue;
449 }
450 /* escape char (preserve in quotes - unless escaping the same quote) */
451 if (c == '\\')
452 {
453 if ( state==IN_QUOTE && quote == '\'' ) {
454 SaveChar('\\');
455 continue;
456 }
457 GetChar(c);
458 if (c == '\n')
459 switch (state) {
460 case IN_COMMENT:
461 state=IN_WHITE; /* end comment */
462 magick_fallthrough;
463 case IN_QUOTE:
464 if (quote != '"')
465 break; /* in double quotes only */
466 magick_fallthrough;
467 case IN_WHITE:
468 case IN_TOKEN:
469 continue; /* line continuation - remove line feed */
470 }
471 switch (state) {
472 case IN_WHITE:
473 token_info->token_line=token_info->curr_line;
474 token_info->token_column=token_info->curr_column;
475 state=IN_TOKEN;
476 break;
477 case IN_QUOTE:
478 if (c != quote && c != '\\')
479 SaveChar('\\');
480 break;
481 }
482 SaveChar(c);
483 continue;
484 }
485 /* ordinary character */
486 switch (state) {
487 case IN_WHITE:
488 token_info->token_line=token_info->curr_line;
489 token_info->token_column=token_info->curr_column;
490 state=IN_TOKEN;
491 magick_fallthrough;
492 case IN_TOKEN:
493 case IN_QUOTE:
494 SaveChar(c);
495 break;
496 case IN_COMMENT:
497 break;
498 }
499 }
500 /* input stream has EOF or produced a fatal error */
501 token_info->token[offset]='\0';
502 if ( token_info->status != TokenStatusOK )
503 return(MagickFalse); /* fatal condition - no valid token */
504 token_info->status = TokenStatusEOF;
505 if ( state == IN_QUOTE)
506 token_info->status = TokenStatusBadQuotes;
507 if ( state == IN_TOKEN)
508 return(MagickTrue); /* token with EOF at end - no problem */
509 return(MagickFalse); /* in white space or in quotes - invalid token */
510}