• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2008, http://www.snakeyaml.org
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.yaml.snakeyaml.constructor;
17 
18 import java.math.BigInteger;
19 import java.text.NumberFormat;
20 import java.text.ParseException;
21 import java.util.ArrayList;
22 import java.util.Calendar;
23 import java.util.Collections;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.LinkedHashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Set;
30 import java.util.TimeZone;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 
34 import org.yaml.snakeyaml.error.YAMLException;
35 import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
36 import org.yaml.snakeyaml.nodes.MappingNode;
37 import org.yaml.snakeyaml.nodes.Node;
38 import org.yaml.snakeyaml.nodes.NodeId;
39 import org.yaml.snakeyaml.nodes.NodeTuple;
40 import org.yaml.snakeyaml.nodes.ScalarNode;
41 import org.yaml.snakeyaml.nodes.SequenceNode;
42 import org.yaml.snakeyaml.nodes.Tag;
43 
44 /**
45  * Construct standard Java classes
46  */
47 public class SafeConstructor extends BaseConstructor {
48 
49     public static final ConstructUndefined undefinedConstructor = new ConstructUndefined();
50 
SafeConstructor()51     public SafeConstructor() {
52         this.yamlConstructors.put(Tag.NULL, new ConstructYamlNull());
53         this.yamlConstructors.put(Tag.BOOL, new ConstructYamlBool());
54         this.yamlConstructors.put(Tag.INT, new ConstructYamlInt());
55         this.yamlConstructors.put(Tag.FLOAT, new ConstructYamlFloat());
56         this.yamlConstructors.put(Tag.BINARY, new ConstructYamlBinary());
57         this.yamlConstructors.put(Tag.TIMESTAMP, new ConstructYamlTimestamp());
58         this.yamlConstructors.put(Tag.OMAP, new ConstructYamlOmap());
59         this.yamlConstructors.put(Tag.PAIRS, new ConstructYamlPairs());
60         this.yamlConstructors.put(Tag.SET, new ConstructYamlSet());
61         this.yamlConstructors.put(Tag.STR, new ConstructYamlStr());
62         this.yamlConstructors.put(Tag.SEQ, new ConstructYamlSeq());
63         this.yamlConstructors.put(Tag.MAP, new ConstructYamlMap());
64         this.yamlConstructors.put(null, undefinedConstructor);
65         this.yamlClassConstructors.put(NodeId.scalar, undefinedConstructor);
66         this.yamlClassConstructors.put(NodeId.sequence, undefinedConstructor);
67         this.yamlClassConstructors.put(NodeId.mapping, undefinedConstructor);
68     }
69 
flattenMapping(MappingNode node)70     protected void flattenMapping(MappingNode node) {
71         // perform merging only on nodes containing merge node(s)
72         if (node.isMerged()) {
73             node.setValue(mergeNode(node, true, new HashMap<Object, Integer>(),
74                     new ArrayList<NodeTuple>()));
75         }
76     }
77 
78     /**
79      * Does merge for supplied mapping node.
80      *
81      * @param node
82      *            where to merge
83      * @param isPreffered
84      *            true if keys of node should take precedence over others...
85      * @param key2index
86      *            maps already merged keys to index from values
87      * @param values
88      *            collects merged NodeTuple
89      * @return list of the merged NodeTuple (to be set as value for the
90      *         MappingNode)
91      */
mergeNode(MappingNode node, boolean isPreffered, Map<Object, Integer> key2index, List<NodeTuple> values)92     private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered,
93             Map<Object, Integer> key2index, List<NodeTuple> values) {
94         List<NodeTuple> nodeValue = node.getValue();
95         // reversed for http://code.google.com/p/snakeyaml/issues/detail?id=139
96         Collections.reverse(nodeValue);
97         for (Iterator<NodeTuple> iter = nodeValue.iterator(); iter.hasNext();) {
98             final NodeTuple nodeTuple = iter.next();
99             final Node keyNode = nodeTuple.getKeyNode();
100             final Node valueNode = nodeTuple.getValueNode();
101             if (keyNode.getTag().equals(Tag.MERGE)) {
102                 iter.remove();
103                 switch (valueNode.getNodeId()) {
104                 case mapping:
105                     MappingNode mn = (MappingNode) valueNode;
106                     mergeNode(mn, false, key2index, values);
107                     break;
108                 case sequence:
109                     SequenceNode sn = (SequenceNode) valueNode;
110                     List<Node> vals = sn.getValue();
111                     for (Node subnode : vals) {
112                         if (!(subnode instanceof MappingNode)) {
113                             throw new ConstructorException("while constructing a mapping",
114                                     node.getStartMark(),
115                                     "expected a mapping for merging, but found "
116                                             + subnode.getNodeId(), subnode.getStartMark());
117                         }
118                         MappingNode mnode = (MappingNode) subnode;
119                         mergeNode(mnode, false, key2index, values);
120                     }
121                     break;
122                 default:
123                     throw new ConstructorException("while constructing a mapping",
124                             node.getStartMark(),
125                             "expected a mapping or list of mappings for merging, but found "
126                                     + valueNode.getNodeId(), valueNode.getStartMark());
127                 }
128             } else {
129                 // we need to construct keys to avoid duplications
130                 Object key = constructObject(keyNode);
131                 if (!key2index.containsKey(key)) { // 1st time merging key
132                     values.add(nodeTuple);
133                     // keep track where tuple for the key is
134                     key2index.put(key, values.size() - 1);
135                 } else if (isPreffered) { // there is value for the key, but we
136                                           // need to override it
137                     // change value for the key using saved position
138                     values.set(key2index.get(key), nodeTuple);
139                 }
140             }
141         }
142         return values;
143     }
144 
constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping)145     protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
146         flattenMapping(node);
147         super.constructMapping2ndStep(node, mapping);
148     }
149 
150     @Override
constructSet2ndStep(MappingNode node, Set<Object> set)151     protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
152         flattenMapping(node);
153         super.constructSet2ndStep(node, set);
154     }
155 
156     public class ConstructYamlNull extends AbstractConstruct {
construct(Node node)157         public Object construct(Node node) {
158             constructScalar((ScalarNode) node);
159             return null;
160         }
161     }
162 
163     private final static Map<String, Boolean> BOOL_VALUES = new HashMap<String, Boolean>();
164     static {
165         BOOL_VALUES.put("yes", Boolean.TRUE);
166         BOOL_VALUES.put("no", Boolean.FALSE);
167         BOOL_VALUES.put("true", Boolean.TRUE);
168         BOOL_VALUES.put("false", Boolean.FALSE);
169         BOOL_VALUES.put("on", Boolean.TRUE);
170         BOOL_VALUES.put("off", Boolean.FALSE);
171     }
172 
173     public class ConstructYamlBool extends AbstractConstruct {
construct(Node node)174         public Object construct(Node node) {
175             String val = (String) constructScalar((ScalarNode) node);
176             return BOOL_VALUES.get(val.toLowerCase());
177         }
178     }
179 
180     public class ConstructYamlInt extends AbstractConstruct {
construct(Node node)181         public Object construct(Node node) {
182             String value = constructScalar((ScalarNode) node).toString().replaceAll("_", "");
183             int sign = +1;
184             char first = value.charAt(0);
185             if (first == '-') {
186                 sign = -1;
187                 value = value.substring(1);
188             } else if (first == '+') {
189                 value = value.substring(1);
190             }
191             int base = 10;
192             if ("0".equals(value)) {
193                 return Integer.valueOf(0);
194             } else if (value.startsWith("0b")) {
195                 value = value.substring(2);
196                 base = 2;
197             } else if (value.startsWith("0x")) {
198                 value = value.substring(2);
199                 base = 16;
200             } else if (value.startsWith("0")) {
201                 value = value.substring(1);
202                 base = 8;
203             } else if (value.indexOf(':') != -1) {
204                 String[] digits = value.split(":");
205                 int bes = 1;
206                 int val = 0;
207                 for (int i = 0, j = digits.length; i < j; i++) {
208                     val += Long.parseLong(digits[j - i - 1]) * bes;
209                     bes *= 60;
210                 }
211                 return createNumber(sign, String.valueOf(val), 10);
212             } else {
213                 return createNumber(sign, value, 10);
214             }
215             return createNumber(sign, value, base);
216         }
217     }
218 
createNumber(int sign, String number, int radix)219     private Number createNumber(int sign, String number, int radix) {
220         Number result;
221         if (sign < 0) {
222             number = "-" + number;
223         }
224         try {
225             result = Integer.valueOf(number, radix);
226         } catch (NumberFormatException e) {
227             try {
228                 result = Long.valueOf(number, radix);
229             } catch (NumberFormatException e1) {
230                 result = new BigInteger(number, radix);
231             }
232         }
233         return result;
234     }
235 
236     public class ConstructYamlFloat extends AbstractConstruct {
construct(Node node)237         public Object construct(Node node) {
238             String value = constructScalar((ScalarNode) node).toString().replaceAll("_", "");
239             int sign = +1;
240             char first = value.charAt(0);
241             if (first == '-') {
242                 sign = -1;
243                 value = value.substring(1);
244             } else if (first == '+') {
245                 value = value.substring(1);
246             }
247             String valLower = value.toLowerCase();
248             if (".inf".equals(valLower)) {
249                 return new Double(sign == -1 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
250             } else if (".nan".equals(valLower)) {
251                 return new Double(Double.NaN);
252             } else if (value.indexOf(':') != -1) {
253                 String[] digits = value.split(":");
254                 int bes = 1;
255                 double val = 0.0;
256                 for (int i = 0, j = digits.length; i < j; i++) {
257                     val += Double.parseDouble(digits[j - i - 1]) * bes;
258                     bes *= 60;
259                 }
260                 return new Double(sign * val);
261             } else {
262                 Double d = Double.valueOf(value);
263                 return new Double(d.doubleValue() * sign);
264             }
265         }
266     }
267 
268     public class ConstructYamlBinary extends AbstractConstruct {
construct(Node node)269         public Object construct(Node node) {
270             byte[] decoded = Base64Coder.decode(constructScalar((ScalarNode) node).toString()
271                     .toCharArray());
272             return decoded;
273         }
274     }
275 
276     public class ConstructYamlNumber extends AbstractConstruct {
277 
278         private final NumberFormat nf = NumberFormat.getInstance();
279 
construct(Node node)280         public Object construct(Node node) {
281             ScalarNode scalar = (ScalarNode) node;
282             try {
283                 return nf.parse(scalar.getValue());
284             } catch (ParseException e) {
285                 String lowerCaseValue = scalar.getValue().toLowerCase();
286                 if (lowerCaseValue.contains("inf") || lowerCaseValue.contains("nan")) {
287                     /*
288                      * Non-finites such as (+/-)infinity and NaN are not
289                      * parseable by NumberFormat when these `Double` values are
290                      * dumped by snakeyaml. Delegate to the `Tag.FLOAT`
291                      * constructor when for this expected failure cause.
292                      */
293                     return (Number) yamlConstructors.get(Tag.FLOAT).construct(node);
294                 } else {
295                     throw new IllegalArgumentException("Unable to parse as Number: "
296                             + scalar.getValue());
297                 }
298             }
299         }
300     }
301 
302     private final static Pattern TIMESTAMP_REGEXP = Pattern
303             .compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)(?:(?:[Tt]|[ \t]+)([0-9][0-9]?):([0-9][0-9]):([0-9][0-9])(?:\\.([0-9]*))?(?:[ \t]*(?:Z|([-+][0-9][0-9]?)(?::([0-9][0-9])?)?))?)?$");
304     private final static Pattern YMD_REGEXP = Pattern
305             .compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$");
306 
307     public static class ConstructYamlTimestamp extends AbstractConstruct {
308         private Calendar calendar;
309 
getCalendar()310         public Calendar getCalendar() {
311             return calendar;
312         }
313 
construct(Node node)314         public Object construct(Node node) {
315             ScalarNode scalar = (ScalarNode) node;
316             String nodeValue = scalar.getValue();
317             Matcher match = YMD_REGEXP.matcher(nodeValue);
318             if (match.matches()) {
319                 String year_s = match.group(1);
320                 String month_s = match.group(2);
321                 String day_s = match.group(3);
322                 calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
323                 calendar.clear();
324                 calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
325                 // Java's months are zero-based...
326                 calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x
327                 calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
328                 return calendar.getTime();
329             } else {
330                 match = TIMESTAMP_REGEXP.matcher(nodeValue);
331                 if (!match.matches()) {
332                     throw new YAMLException("Unexpected timestamp: " + nodeValue);
333                 }
334                 String year_s = match.group(1);
335                 String month_s = match.group(2);
336                 String day_s = match.group(3);
337                 String hour_s = match.group(4);
338                 String min_s = match.group(5);
339                 // seconds and milliseconds
340                 String seconds = match.group(6);
341                 String millis = match.group(7);
342                 if (millis != null) {
343                     seconds = seconds + "." + millis;
344                 }
345                 double fractions = Double.parseDouble(seconds);
346                 int sec_s = (int) Math.round(Math.floor(fractions));
347                 int usec = (int) Math.round((fractions - sec_s) * 1000);
348                 // timezone
349                 String timezoneh_s = match.group(8);
350                 String timezonem_s = match.group(9);
351                 TimeZone timeZone;
352                 if (timezoneh_s != null) {
353                     String time = timezonem_s != null ? ":" + timezonem_s : "00";
354                     timeZone = TimeZone.getTimeZone("GMT" + timezoneh_s + time);
355                 } else {
356                     // no time zone provided
357                     timeZone = TimeZone.getTimeZone("UTC");
358                 }
359                 calendar = Calendar.getInstance(timeZone);
360                 calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
361                 // Java's months are zero-based...
362                 calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1);
363                 calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
364                 calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour_s));
365                 calendar.set(Calendar.MINUTE, Integer.parseInt(min_s));
366                 calendar.set(Calendar.SECOND, sec_s);
367                 calendar.set(Calendar.MILLISECOND, usec);
368                 return calendar.getTime();
369             }
370         }
371     }
372 
373     public class ConstructYamlOmap extends AbstractConstruct {
construct(Node node)374         public Object construct(Node node) {
375             // Note: we do not check for duplicate keys, because it's too
376             // CPU-expensive.
377             Map<Object, Object> omap = new LinkedHashMap<Object, Object>();
378             if (!(node instanceof SequenceNode)) {
379                 throw new ConstructorException("while constructing an ordered map",
380                         node.getStartMark(), "expected a sequence, but found " + node.getNodeId(),
381                         node.getStartMark());
382             }
383             SequenceNode snode = (SequenceNode) node;
384             for (Node subnode : snode.getValue()) {
385                 if (!(subnode instanceof MappingNode)) {
386                     throw new ConstructorException("while constructing an ordered map",
387                             node.getStartMark(), "expected a mapping of length 1, but found "
388                                     + subnode.getNodeId(), subnode.getStartMark());
389                 }
390                 MappingNode mnode = (MappingNode) subnode;
391                 if (mnode.getValue().size() != 1) {
392                     throw new ConstructorException("while constructing an ordered map",
393                             node.getStartMark(), "expected a single mapping item, but found "
394                                     + mnode.getValue().size() + " items", mnode.getStartMark());
395                 }
396                 Node keyNode = mnode.getValue().get(0).getKeyNode();
397                 Node valueNode = mnode.getValue().get(0).getValueNode();
398                 Object key = constructObject(keyNode);
399                 Object value = constructObject(valueNode);
400                 omap.put(key, value);
401             }
402             return omap;
403         }
404     }
405 
406     // Note: the same code as `construct_yaml_omap`.
407     public class ConstructYamlPairs extends AbstractConstruct {
construct(Node node)408         public Object construct(Node node) {
409             // Note: we do not check for duplicate keys, because it's too
410             // CPU-expensive.
411             if (!(node instanceof SequenceNode)) {
412                 throw new ConstructorException("while constructing pairs", node.getStartMark(),
413                         "expected a sequence, but found " + node.getNodeId(), node.getStartMark());
414             }
415             SequenceNode snode = (SequenceNode) node;
416             List<Object[]> pairs = new ArrayList<Object[]>(snode.getValue().size());
417             for (Node subnode : snode.getValue()) {
418                 if (!(subnode instanceof MappingNode)) {
419                     throw new ConstructorException("while constructingpairs", node.getStartMark(),
420                             "expected a mapping of length 1, but found " + subnode.getNodeId(),
421                             subnode.getStartMark());
422                 }
423                 MappingNode mnode = (MappingNode) subnode;
424                 if (mnode.getValue().size() != 1) {
425                     throw new ConstructorException("while constructing pairs", node.getStartMark(),
426                             "expected a single mapping item, but found " + mnode.getValue().size()
427                                     + " items", mnode.getStartMark());
428                 }
429                 Node keyNode = mnode.getValue().get(0).getKeyNode();
430                 Node valueNode = mnode.getValue().get(0).getValueNode();
431                 Object key = constructObject(keyNode);
432                 Object value = constructObject(valueNode);
433                 pairs.add(new Object[] { key, value });
434             }
435             return pairs;
436         }
437     }
438 
439     public class ConstructYamlSet implements Construct {
construct(Node node)440         public Object construct(Node node) {
441             if (node.isTwoStepsConstruction()) {
442                 return createDefaultSet();
443             } else {
444                 return constructSet((MappingNode) node);
445             }
446         }
447 
448         @SuppressWarnings("unchecked")
construct2ndStep(Node node, Object object)449         public void construct2ndStep(Node node, Object object) {
450             if (node.isTwoStepsConstruction()) {
451                 constructSet2ndStep((MappingNode) node, (Set<Object>) object);
452             } else {
453                 throw new YAMLException("Unexpected recursive set structure. Node: " + node);
454             }
455         }
456     }
457 
458     public class ConstructYamlStr extends AbstractConstruct {
construct(Node node)459         public Object construct(Node node) {
460             return constructScalar((ScalarNode) node);
461         }
462     }
463 
464     public class ConstructYamlSeq implements Construct {
construct(Node node)465         public Object construct(Node node) {
466             SequenceNode seqNode = (SequenceNode) node;
467             if (node.isTwoStepsConstruction()) {
468                 return createDefaultList(seqNode.getValue().size());
469             } else {
470                 return constructSequence(seqNode);
471             }
472         }
473 
474         @SuppressWarnings("unchecked")
construct2ndStep(Node node, Object data)475         public void construct2ndStep(Node node, Object data) {
476             if (node.isTwoStepsConstruction()) {
477                 constructSequenceStep2((SequenceNode) node, (List<Object>) data);
478             } else {
479                 throw new YAMLException("Unexpected recursive sequence structure. Node: " + node);
480             }
481         }
482     }
483 
484     public class ConstructYamlMap implements Construct {
construct(Node node)485         public Object construct(Node node) {
486             if (node.isTwoStepsConstruction()) {
487                 return createDefaultMap();
488             } else {
489                 return constructMapping((MappingNode) node);
490             }
491         }
492 
493         @SuppressWarnings("unchecked")
construct2ndStep(Node node, Object object)494         public void construct2ndStep(Node node, Object object) {
495             if (node.isTwoStepsConstruction()) {
496                 constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
497             } else {
498                 throw new YAMLException("Unexpected recursive mapping structure. Node: " + node);
499             }
500         }
501     }
502 
503     public static final class ConstructUndefined extends AbstractConstruct {
construct(Node node)504         public Object construct(Node node) {
505             throw new ConstructorException(null, null,
506                     "could not determine a constructor for the tag " + node.getTag(),
507                     node.getStartMark());
508         }
509     }
510 }
511