]> granicus.if.org Git - imagemagick/blob - MagickWand/script-token.c
(no commit message)
[imagemagick] / MagickWand / 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 %       Perform "Magick" on Images via the Command Line Interface             %
12 %                                                                             %
13 %                             Dragon Computing                                %
14 %                             Anthony Thyssen                                 %
15 %                               January 2012                                  %
16 %                                                                             %
17 %                                                                             %
18 %  Copyright 1999-2012 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 %    http://www.imagemagick.org/script/license.php                            %
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 */
37 \f
38 /*
39   Include declarations.
40
41   NOTE: Do not include if being compiled into the "test/script-token-test.c"
42   module, for low level token testing.
43 */
44 #ifndef SCRIPT_TOKEN_TESTING
45 #  include "MagickWand/studio.h"
46 #  include "MagickWand/MagickWand.h"
47 #  include "MagickWand/script-token.h"
48 #  include "MagickCore/string-private.h"
49 #  include "MagickCore/utility-private.h"
50 #endif
51 \f
52 /*
53 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
54 %                                                                             %
55 %                                                                             %
56 %                                                                             %
57 %   A c q u i r e S c r i p t T o k e n I n f o                               %
58 %                                                                             %
59 %                                                                             %
60 %                                                                             %
61 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
62 %
63 %  AcquireScriptTokenInfo() allocated, initializes and opens the given
64 %  file stream from which tokens are to be extracted.
65 %
66 %  The format of the AcquireScriptTokenInfo method is:
67 %
68 %     ScriptTokenInfo *AcquireScriptTokenInfo(char *filename)
69 %
70 %  A description of each parameter follows:
71 %
72 %    o filename   the filename to open  ("-" means stdin)
73 %
74 */
75 WandExport ScriptTokenInfo *AcquireScriptTokenInfo(char *filename)
76 {
77   ScriptTokenInfo
78     *token_info;
79
80   token_info=(ScriptTokenInfo *) AcquireMagickMemory(sizeof(*token_info));
81   if (token_info == (ScriptTokenInfo *) NULL)
82     return token_info;
83   (void) ResetMagickMemory(token_info,0,sizeof(*token_info));
84
85   token_info->opened=MagickFalse;
86   if ( LocaleCompare(filename,"-") == 0 ) {
87     token_info->stream=stdin;
88     token_info->opened=MagickFalse;
89   }
90 #if 0  /* FUTURE POSIBILITIES */
91   else if ( LocaleNCompare(filename,"fd:",3) == 0 ) {
92     token_info->stream=fdopen(StringToLong(filename+3),"r");
93     token_info->opened=MagickFalse;
94   }
95 #endif
96   else {
97     token_info->stream=fopen(filename, "r");
98   }
99   if ( token_info->stream == (FILE *)NULL ) {
100     token_info=(ScriptTokenInfo *) RelinquishMagickMemory(token_info);
101     return(token_info);
102   }
103
104   token_info->curr_line=1;
105   token_info->length=INITAL_TOKEN_LENGTH;
106   token_info->token=(char *) AcquireMagickMemory(token_info->length);
107
108   token_info->status=(token_info->token != (char *)NULL)
109                       ? TokenStatusOK : TokenStatusMemoryFailed;
110   token_info->signature=WandSignature;
111
112   return token_info;
113 }
114 \f
115 /*
116 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
117 %                                                                             %
118 %                                                                             %
119 %                                                                             %
120 %   D e s t r o y S c r i p t T o k e n I n f o                               %
121 %                                                                             %
122 %                                                                             %
123 %                                                                             %
124 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
125 %
126 %  DestroyScriptTokenInfo() allocated, initializes and opens the given
127 %  file stream from which tokens are to be extracted.
128 %
129 %  The format of the DestroyScriptTokenInfo method is:
130 %
131 %     ScriptTokenInfo *DestroyScriptTokenInfo(ScriptTokenInfo *token_info)
132 %
133 %  A description of each parameter follows:
134 %
135 %    o token_info   The ScriptTokenInfo structure to be destroyed
136 %
137 */
138 WandExport ScriptTokenInfo * DestroyScriptTokenInfo(ScriptTokenInfo *token_info)
139 {
140   assert(token_info != (ScriptTokenInfo *) NULL);
141   assert(token_info->signature == WandSignature);
142
143   if ( token_info->opened != MagickFalse )
144     fclose(token_info->stream);
145
146   if (token_info->token != (char *) NULL )
147     token_info->token=(char *) RelinquishMagickMemory(token_info->token);
148   token_info=(ScriptTokenInfo *) RelinquishMagickMemory(token_info);
149   return(token_info);
150 }
151 \f
152 /*
153 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
154 %                                                                             %
155 %                                                                             %
156 %                                                                             %
157 %   G e t S c r i p t T o k e n                                               %
158 %                                                                             %
159 %                                                                             %
160 %                                                                             %
161 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
162 %
163 %  GetScriptToken() is fairly general, finite state token parser. That will
164 %  divide a input file stream into tokens, in a way that is as close to a
165 %  UNIX shell, as is feasable.  Only shell variable, and command
166 %  substitutions will not be performed.  Tokens can be any length.
167 %
168 %  Tokens are white space separated, and may be quoted, or even partially
169 %  quoted by either single or double quotes, or the use of backslashes,
170 %  or any mix of the three.
171 %
172 %  For example:    This\ is' a 'single" token"
173 %
174 %  A token is returned immediatally the end of token is found. That is as soon
175 %  as a unquoted white-space or EOF condition has been found.  That is to say
176 %  the file stream is parsed purely character-by-character, regardless any
177 %  buffering constraints set by the system.  It is not parsed line-by-line.
178 %
179 %  The function will return 'MagickTrue' if a valid token was found, while
180 %  the token status will be set accordingally to 'OK' or 'EOF', according to
181 %  the cause of the end of token.  The token may be an empty string if the
182 %  input was a quoted empty string.  Other error conditions return a value of
183 %  MagickFalse, indicating any token found but was incomplete due to some
184 %  error condition.
185 %
186 %  Single quotes will preserve all characters including backslashes. Double
187 %  quotes will also preserve backslashes unless escaping a double quote,
188 %  or another backslashes.  Other shell meta-characters are not treated as
189 %  special by this tokenizer.
190 %
191 %  For example Quoting the quote chars:
192 %              \'  "'"       \"  '"'  "\""      \\  '\'  "\\"
193 %
194 %  Outside quotes, backslash characters will make spaces, tabs and quotes part
195 %  of a token returned. However a backslash at the end of a line (and outside
196 %  quotes) will cause the newline to be completely ignored (as per the shell
197 %  line continuation).
198 %
199 %  Comments start with a '#' character at the start of a new token, will be
200 %  completely ignored upto the end of line, regardless of any backslash at the
201 %  end of the line.  You can escape a comment '#', using quotes or backlsashes
202 %  just as you can in a shell.
203 %
204 %  As a special case a ':' at the start of a line is also treated as a comment
205 %  This allows a magick script to ignore a line that is parsed by the shell
206 %  and can be used as a 'launcher' for magick scripts. For example
207 %
208 %    #!/bin/sh
209 %    #
210 %    # Shell Launcher for Magick Script
211 %    : echo "This part is run in the shell"
212 %    : exec magick -script "$0" "$@"; exit 10
213 %    #
214 %    # The rest of the script is magick script
215 %    -read label:"This is a Magick Script!"
216 %    -write show: -exit
217 %
218 %
219 %  The format of the GetScriptToken method is:
220 %
221 %     MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
222 %
223 %  A description of each parameter follows:
224 %
225 %    o token_info    pointer to a structure holding token details
226 %
227 */
228 /* States of the parser */
229 #define IN_WHITE 0
230 #define IN_TOKEN 1
231 #define IN_QUOTE 2
232 #define IN_COMMENT 3
233
234 /* Macro to read character from stream
235
236    This also keeps track of the line and column counts.
237    The EOL is defined as either '\r\n', or '\r', or '\n'.
238    A '\r' on its own is converted into a '\n' to correctly handle
239    raw input, typically due to 'copy-n-paste' of text files.
240 */
241 #define GetChar(c) \
242 { \
243   c=fgetc(token_info->stream); \
244   token_info->curr_column++; \
245   if ( c == '\r' ) { \
246     c=fgetc(token_info->stream); \
247     ungetc(c,token_info->stream); \
248     c = (c!='\n')?'\n':'\r'; \
249   } \
250   if ( c == '\n' ) \
251     token_info->curr_line++, token_info->curr_column=0; \
252   if (c == EOF ) \
253     break; \
254   if ( (c>='\0' && c<'\a') || (c>'\r' && c<' ' && c!='\033') ) { \
255     token_info->status=TokenStatusBinary; \
256     break; \
257   } \
258 }
259 /* macro to collect the token characters */
260 #define SaveChar(c) \
261 { \
262   if ((size_t) offset >= (token_info->length-1)) { \
263     if ( token_info->length >= MaxTextExtent ) \
264       token_info->length += MaxTextExtent; \
265     else \
266       token_info->length *= 4; \
267     token_info->token = (char *) \
268          ResizeMagickMemory(token_info->token, token_info->length); \
269     if ( token_info->token == (char *)NULL ) { \
270       token_info->status=TokenStatusMemoryFailed; \
271       break; \
272     } \
273   } \
274   token_info->token[offset++]=(char) (c); \
275 }
276
277 WandExport MagickBooleanType GetScriptToken(ScriptTokenInfo *token_info)
278 {
279   int
280     quote,
281     c;
282
283   int
284     state;
285
286   ssize_t
287     offset;
288
289   /* EOF - no more tokens! */
290   if (token_info->status != TokenStatusOK)
291     {
292       token_info->token[0]='\0';
293       return(MagickFalse);
294     }
295
296   state=IN_WHITE;
297   quote='\0';
298   offset=0;
299   while(1)
300   {
301     /* get character */
302     GetChar(c);
303
304     /* hash comment handling */
305     if ( state == IN_COMMENT ) {
306       if ( c == '\n' )
307         state=IN_WHITE;
308       continue;
309     }
310     if ( state == IN_WHITE )
311       if (c == '#' || (c == ':' && token_info->curr_column==1))
312         state=IN_COMMENT;
313     /* whitespace break character */
314     if (strchr(" \n\r\t",c) != (char *)NULL) {
315       switch (state) {
316         case IN_TOKEN:
317           token_info->token[offset]='\0';
318           return(MagickTrue);
319         case IN_QUOTE:
320           SaveChar(c);
321           break;
322       }
323       continue;
324     }
325     /* quote character */
326     if (strchr("'\"",c) != (char *)NULL) {
327       switch (state) {
328         case IN_WHITE:
329           token_info->token_line=token_info->curr_line;
330           token_info->token_column=token_info->curr_column;
331         case IN_TOKEN:
332           state=IN_QUOTE;
333           quote=c;
334           break;
335         case IN_QUOTE:
336           if (c == quote)
337             {
338               state=IN_TOKEN;
339               quote='\0';
340             }
341           else
342             SaveChar(c);
343           break;
344       }
345       continue;
346     }
347     /* escape char (preserve in quotes - unless escaping the same quote) */
348     if (c == '\\')
349       {
350         if ( state==IN_QUOTE && quote == '\'' ) {
351             SaveChar('\\');
352             continue;
353           }
354         GetChar(c);
355         if (c == '\n' || c == '\r' )
356           switch (state) {
357             case IN_COMMENT:
358               state=IN_WHITE;  /* end comment */
359             case IN_WHITE:
360             case IN_TOKEN:
361               continue;   /* line continuation (outside quotes and comment) */
362           }
363         switch (state) {
364           case IN_WHITE:
365             token_info->token_line=token_info->curr_line;
366             token_info->token_column=token_info->curr_column;
367             state=IN_TOKEN;
368             break;
369           case IN_QUOTE:
370             if (c != quote && c != '\\')
371               SaveChar('\\');
372             break;
373         }
374         SaveChar(c);
375         continue;
376       }
377     /* ordinary character */
378     switch (state) {
379       case IN_WHITE:
380         token_info->token_line=token_info->curr_line;
381         token_info->token_column=token_info->curr_column;
382         state=IN_TOKEN;
383       case IN_TOKEN:
384       case IN_QUOTE:
385         SaveChar(c);
386         break;
387       case IN_COMMENT:
388         break;
389     }
390   }
391   /* input stream has EOF or produced a fatal error */
392   token_info->token[offset]='\0';
393   if ( token_info->status != TokenStatusOK )
394     return(MagickFalse);  /* fatal condition - no valid token */
395   token_info->status = TokenStatusEOF;
396   if ( state == IN_QUOTE)
397     token_info->status = TokenStatusBadQuotes;
398   if ( state == IN_TOKEN)
399     return(MagickTrue);   /* token with EOF at end - no problem */
400   return(MagickFalse);    /* in white space or in quotes - invalid token */
401 }