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.util.ArrayList;
20 import java.util.Calendar;
21 import java.util.HashMap;
22 import java.util.Iterator;
23 import java.util.LinkedHashMap;
24 import java.util.List;
25 import java.util.Map;
26 import java.util.Set;
27 import java.util.TimeZone;
28 import java.util.TreeSet;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import org.yaml.snakeyaml.LoaderOptions;
33 import org.yaml.snakeyaml.error.YAMLException;
34 import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
35 import org.yaml.snakeyaml.nodes.MappingNode;
36 import org.yaml.snakeyaml.nodes.Node;
37 import org.yaml.snakeyaml.nodes.NodeId;
38 import org.yaml.snakeyaml.nodes.NodeTuple;
39 import org.yaml.snakeyaml.nodes.ScalarNode;
40 import org.yaml.snakeyaml.nodes.SequenceNode;
41 import org.yaml.snakeyaml.nodes.Tag;
42
43 /**
44  * Construct standard Java classes
45  */

46 public class SafeConstructor extends BaseConstructor {
47
48     public static final ConstructUndefined undefinedConstructor = new ConstructUndefined();
49
50     public SafeConstructor() {
51         this(new LoaderOptions());
52     }
53
54     public SafeConstructor(LoaderOptions loadingConfig) {
55         super(loadingConfig);
56         this.yamlConstructors.put(Tag.NULL, new ConstructYamlNull());
57         this.yamlConstructors.put(Tag.BOOL, new ConstructYamlBool());
58         this.yamlConstructors.put(Tag.INT, new ConstructYamlInt());
59         this.yamlConstructors.put(Tag.FLOAT, new ConstructYamlFloat());
60         this.yamlConstructors.put(Tag.BINARY, new ConstructYamlBinary());
61         this.yamlConstructors.put(Tag.TIMESTAMP, new ConstructYamlTimestamp());
62         this.yamlConstructors.put(Tag.OMAP, new ConstructYamlOmap());
63         this.yamlConstructors.put(Tag.PAIRS, new ConstructYamlPairs());
64         this.yamlConstructors.put(Tag.SET, new ConstructYamlSet());
65         this.yamlConstructors.put(Tag.STR, new ConstructYamlStr());
66         this.yamlConstructors.put(Tag.SEQ, new ConstructYamlSeq());
67         this.yamlConstructors.put(Tag.MAP, new ConstructYamlMap());
68         this.yamlConstructors.put(null, undefinedConstructor);
69         this.yamlClassConstructors.put(NodeId.scalar, undefinedConstructor);
70         this.yamlClassConstructors.put(NodeId.sequence, undefinedConstructor);
71         this.yamlClassConstructors.put(NodeId.mapping, undefinedConstructor);
72     }
73
74     protected void flattenMapping(MappingNode node) {
75         // perform merging only on nodes containing merge node(s)
76         processDuplicateKeys(node);
77         if (node.isMerged()) {
78             node.setValue(mergeNode(node, truenew HashMap<Object, Integer>(),
79                     new ArrayList<NodeTuple>()));
80         }
81     }
82
83     protected void processDuplicateKeys(MappingNode node) {
84         List<NodeTuple> nodeValue = node.getValue();
85         Map<Object, Integer> keys = new HashMap<Object, Integer>(nodeValue.size());
86         TreeSet<Integer> toRemove = new TreeSet<Integer>();
87         int i = 0;
88         for (NodeTuple tuple : nodeValue) {
89             Node keyNode = tuple.getKeyNode();
90             if (!keyNode.getTag().equals(Tag.MERGE)) {
91                 Object key = constructObject(keyNode);
92                 if (key != null) {
93                     try {
94                         key.hashCode();// check circular dependencies
95                     } catch (Exception e) {
96                         throw new ConstructorException("while constructing a mapping",
97                                 node.getStartMark(), "found unacceptable key " + key,
98                                 tuple.getKeyNode().getStartMark(), e);
99                     }
100                 }
101
102                 Integer prevIndex = keys.put(key, i);
103                 if (prevIndex != null) {
104                     if (!isAllowDuplicateKeys()) {
105                         throw new DuplicateKeyException(node.getStartMark(), key,
106                                 tuple.getKeyNode().getStartMark());
107                     }
108                     toRemove.add(prevIndex);
109                 }
110             }
111             i = i + 1;
112         }
113
114         Iterator<Integer> indicies2remove = toRemove.descendingIterator();
115         while (indicies2remove.hasNext()) {
116             nodeValue.remove(indicies2remove.next().intValue());
117         }
118     }
119
120     /**
121      * Does merge for supplied mapping node.
122      *
123      * @param node
124      *            where to merge
125      * @param isPreffered
126      *            true if keys of node should take precedence over others...
127      * @param key2index
128      *            maps already merged keys to index from values
129      * @param values
130      *            collects merged NodeTuple
131      * @return list of the merged NodeTuple (to be set as value for the
132      *         MappingNode)
133      */

134     private List<NodeTuple> mergeNode(MappingNode node, boolean isPreffered,
135             Map<Object, Integer> key2index, List<NodeTuple> values) {
136         Iterator<NodeTuple> iter = node.getValue().iterator();
137         while (iter.hasNext()) {
138             final NodeTuple nodeTuple = iter.next();
139             final Node keyNode = nodeTuple.getKeyNode();
140             final Node valueNode = nodeTuple.getValueNode();
141             if (keyNode.getTag().equals(Tag.MERGE)) {
142                 iter.remove();
143                 switch (valueNode.getNodeId()) {
144                 case mapping:
145                     MappingNode mn = (MappingNode) valueNode;
146                     mergeNode(mn, false, key2index, values);
147                     break;
148                 case sequence:
149                     SequenceNode sn = (SequenceNode) valueNode;
150                     List<Node> vals = sn.getValue();
151                     for (Node subnode : vals) {
152                         if (!(subnode instanceof MappingNode)) {
153                             throw new ConstructorException("while constructing a mapping",
154                                     node.getStartMark(),
155                                     "expected a mapping for merging, but found "
156                                             + subnode.getNodeId(),
157                                     subnode.getStartMark());
158                         }
159                         MappingNode mnode = (MappingNode) subnode;
160                         mergeNode(mnode, false, key2index, values);
161                     }
162                     break;
163                 default:
164                     throw new ConstructorException("while constructing a mapping",
165                             node.getStartMark(),
166                             "expected a mapping or list of mappings for merging, but found "
167                                     + valueNode.getNodeId(),
168                             valueNode.getStartMark());
169                 }
170             } else {
171                 // we need to construct keys to avoid duplications
172                 Object key = constructObject(keyNode);
173                 if (!key2index.containsKey(key)) { // 1st time merging key
174                     values.add(nodeTuple);
175                     // keep track where tuple for the key is
176                     key2index.put(key, values.size() - 1);
177                 } else if (isPreffered) { // there is value for the key, but we
178                                           // need to override it
179                                           // change value for the key using saved position
180                     values.set(key2index.get(key), nodeTuple);
181                 }
182             }
183         }
184         return values;
185     }
186
187     @Override
188     protected void constructMapping2ndStep(MappingNode node, Map<Object, Object> mapping) {
189         flattenMapping(node);
190         super.constructMapping2ndStep(node, mapping);
191     }
192
193     @Override
194     protected void constructSet2ndStep(MappingNode node, Set<Object> set) {
195         flattenMapping(node);
196         super.constructSet2ndStep(node, set);
197     }
198
199     public class ConstructYamlNull extends AbstractConstruct {
200         @Override
201         public Object construct(Node node) {
202             if (node != null) constructScalar((ScalarNode) node);
203             return null;
204         }
205     }
206
207     private final static Map<String, Boolean> BOOL_VALUES = new HashMap<String, Boolean>();
208     static {
209         BOOL_VALUES.put("yes", Boolean.TRUE);
210         BOOL_VALUES.put("no", Boolean.FALSE);
211         BOOL_VALUES.put("true", Boolean.TRUE);
212         BOOL_VALUES.put("false", Boolean.FALSE);
213         BOOL_VALUES.put("on", Boolean.TRUE);
214         BOOL_VALUES.put("off", Boolean.FALSE);
215     }
216
217     public class ConstructYamlBool extends AbstractConstruct {
218         @Override
219         public Object construct(Node node) {
220             String val = (String) constructScalar((ScalarNode) node);
221             return BOOL_VALUES.get(val.toLowerCase());
222         }
223     }
224
225     public class ConstructYamlInt extends AbstractConstruct {
226         @Override
227         public Object construct(Node node) {
228             String value = constructScalar((ScalarNode) node).toString().replaceAll("_""");
229             int sign = +1;
230             char first = value.charAt(0);
231             if (first == '-') {
232                 sign = -1;
233                 value = value.substring(1);
234             } else if (first == '+') {
235                 value = value.substring(1);
236             }
237             int base = 10;
238             if ("0".equals(value)) {
239                 return Integer.valueOf(0);
240             } else if (value.startsWith("0b")) {
241                 value = value.substring(2);
242                 base = 2;
243             } else if (value.startsWith("0x")) {
244                 value = value.substring(2);
245                 base = 16;
246             } else if (value.startsWith("0")) {
247                 value = value.substring(1);
248                 base = 8;
249             } else if (value.indexOf(':') != -1) {
250                 String[] digits = value.split(":");
251                 int bes = 1;
252                 int val = 0;
253                 for (int i = 0, j = digits.length; i < j; i++) {
254                     val += Long.parseLong(digits[j - i - 1]) * bes;
255                     bes *= 60;
256                 }
257                 return createNumber(sign, String.valueOf(val), 10);
258             } else {
259                 return createNumber(sign, value, 10);
260             }
261             return createNumber(sign, value, base);
262         }
263     }
264
265     private static final int[][]  RADIX_MAX = new int[17][2];
266     static {
267         int[] radixList = new int[] {2, 8, 10, 16};
268         forint radix : radixList) {
269             RADIX_MAX[radix] = new int[] { maxLen(Integer.MAX_VALUE,radix), maxLen(Long.MAX_VALUE,radix)};
270         }
271     }
272     
273     private static int maxLen(final int max, final int radix) {
274         return  Integer.toString(max,radix).length();
275     }
276     private static int maxLen(final long max,final int radix) {
277         return  Long.toString(max,radix).length();
278     }
279     private Number createNumber(int sign, String number, int radix) {
280         final int len = number != null ? number.length() : 0;
281         if (sign < 0) {
282             number = "-" + number;
283         }
284         final int[] maxArr = radix < RADIX_MAX.length ?  RADIX_MAX[radix] : null;
285         if (maxArr != null) {
286             final boolean gtInt = len >maxArr[0];
287             if (gtInt) {
288                 if(len > maxArr[1]) {
289                     return new BigInteger(number, radix);
290                 }
291                 return createLongOrBigInteger(number, radix);
292             }
293         }
294         Number result;
295         try {
296             result = Integer.valueOf(number, radix);
297         } catch (NumberFormatException e) {
298             result = createLongOrBigInteger(number, radix);
299         }
300         return result;
301     }
302
303     protected static Number createLongOrBigInteger(final String number,final  int radix) {
304         try {
305             return Long.valueOf(number, radix);
306         } catch (NumberFormatException e1) {
307             return  new BigInteger(number, radix);
308         }
309     }
310
311     public class ConstructYamlFloat extends AbstractConstruct {
312         @Override
313         public Object construct(Node node) {
314             String value = constructScalar((ScalarNode) node).toString().replaceAll("_""");
315             int sign = +1;
316             char first = value.charAt(0);
317             if (first == '-') {
318                 sign = -1;
319                 value = value.substring(1);
320             } else if (first == '+') {
321                 value = value.substring(1);
322             }
323             String valLower = value.toLowerCase();
324             if (".inf".equals(valLower)) {
325                 return Double
326                         .valueOf(sign == -1 ? Double.NEGATIVE_INFINITY : Double.POSITIVE_INFINITY);
327             } else if (".nan".equals(valLower)) {
328                 return Double.valueOf(Double.NaN);
329             } else if (value.indexOf(':') != -1) {
330                 String[] digits = value.split(":");
331                 int bes = 1;
332                 double val = 0.0;
333                 for (int i = 0, j = digits.length; i < j; i++) {
334                     val += Double.parseDouble(digits[j - i - 1]) * bes;
335                     bes *= 60;
336                 }
337                 return Double.valueOf(sign * val);
338             } else {
339                 Double d = Double.valueOf(value);
340                 return Double.valueOf(d.doubleValue() * sign);
341             }
342         }
343     }
344
345     public class ConstructYamlBinary extends AbstractConstruct {
346         @Override
347         public Object construct(Node node) {
348             // Ignore white spaces for base64 encoded scalar
349             String noWhiteSpaces = constructScalar((ScalarNode) node).toString().replaceAll("\\s",
350                     "");
351             byte[] decoded = Base64Coder.decode(noWhiteSpaces.toCharArray());
352             return decoded;
353         }
354     }
355
356     private final static Pattern TIMESTAMP_REGEXP = Pattern.compile(
357             "^([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])?)?))?)?$");
358     private final static Pattern YMD_REGEXP = Pattern
359             .compile("^([0-9][0-9][0-9][0-9])-([0-9][0-9]?)-([0-9][0-9]?)$");
360
361     public static class ConstructYamlTimestamp extends AbstractConstruct {
362         private Calendar calendar;
363
364         public Calendar getCalendar() {
365             return calendar;
366         }
367
368         @Override
369         public Object construct(Node node) {
370             ScalarNode scalar = (ScalarNode) node;
371             String nodeValue = scalar.getValue();
372             Matcher match = YMD_REGEXP.matcher(nodeValue);
373             if (match.matches()) {
374                 String year_s = match.group(1);
375                 String month_s = match.group(2);
376                 String day_s = match.group(3);
377                 calendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
378                 calendar.clear();
379                 calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
380                 // Java's months are zero-based...
381                 calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1); // x
382                 calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
383                 return calendar.getTime();
384             } else {
385                 match = TIMESTAMP_REGEXP.matcher(nodeValue);
386                 if (!match.matches()) {
387                     throw new YAMLException("Unexpected timestamp: " + nodeValue);
388                 }
389                 String year_s = match.group(1);
390                 String month_s = match.group(2);
391                 String day_s = match.group(3);
392                 String hour_s = match.group(4);
393                 String min_s = match.group(5);
394                 // seconds and milliseconds
395                 String seconds = match.group(6);
396                 String millis = match.group(7);
397                 if (millis != null) {
398                     seconds = seconds + "." + millis;
399                 }
400                 double fractions = Double.parseDouble(seconds);
401                 int sec_s = (int) Math.round(Math.floor(fractions));
402                 int usec = (int) Math.round((fractions - sec_s) * 1000);
403                 // timezone
404                 String timezoneh_s = match.group(8);
405                 String timezonem_s = match.group(9);
406                 TimeZone timeZone;
407                 if (timezoneh_s != null) {
408                     String time = timezonem_s != null ? ":" + timezonem_s : "00";
409                     timeZone = TimeZone.getTimeZone("GMT" + timezoneh_s + time);
410                 } else {
411                     // no time zone provided
412                     timeZone = TimeZone.getTimeZone("UTC");
413                 }
414                 calendar = Calendar.getInstance(timeZone);
415                 calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
416                 // Java's months are zero-based...
417                 calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1);
418                 calendar.set(Calendar.DAY_OF_MONTH, Integer.parseInt(day_s));
419                 calendar.set(Calendar.HOUR_OF_DAY, Integer.parseInt(hour_s));
420                 calendar.set(Calendar.MINUTE, Integer.parseInt(min_s));
421                 calendar.set(Calendar.SECOND, sec_s);
422                 calendar.set(Calendar.MILLISECOND, usec);
423                 return calendar.getTime();
424             }
425         }
426     }
427
428     public class ConstructYamlOmap extends AbstractConstruct {
429         @Override
430         public Object construct(Node node) {
431             // Note: we do not check for duplicate keys, because it's too
432             // CPU-expensive.
433             Map<Object, Object> omap = new LinkedHashMap<Object, Object>();
434             if (!(node instanceof SequenceNode)) {
435                 throw new ConstructorException("while constructing an ordered map",
436                         node.getStartMark(), "expected a sequence, but found " + node.getNodeId(),
437                         node.getStartMark());
438             }
439             SequenceNode snode = (SequenceNode) node;
440             for (Node subnode : snode.getValue()) {
441                 if (!(subnode instanceof MappingNode)) {
442                     throw new ConstructorException("while constructing an ordered map",
443                             node.getStartMark(),
444                             "expected a mapping of length 1, but found " + subnode.getNodeId(),
445                             subnode.getStartMark());
446                 }
447                 MappingNode mnode = (MappingNode) subnode;
448                 if (mnode.getValue().size() != 1) {
449                     throw new ConstructorException("while constructing an ordered map",
450                             node.getStartMark(), "expected a single mapping item, but found "
451                                     + mnode.getValue().size() + " items",
452                             mnode.getStartMark());
453                 }
454                 Node keyNode = mnode.getValue().get(0).getKeyNode();
455                 Node valueNode = mnode.getValue().get(0).getValueNode();
456                 Object key = constructObject(keyNode);
457                 Object value = constructObject(valueNode);
458                 omap.put(key, value);
459             }
460             return omap;
461         }
462     }
463
464     public class ConstructYamlPairs extends AbstractConstruct {
465         @Override
466         public Object construct(Node node) {
467             // Note: we do not check for duplicate keys, because it's too
468             // CPU-expensive.
469             if (!(node instanceof SequenceNode)) {
470                 throw new ConstructorException("while constructing pairs", node.getStartMark(),
471                         "expected a sequence, but found " + node.getNodeId(), node.getStartMark());
472             }
473             SequenceNode snode = (SequenceNode) node;
474             List<Object[]> pairs = new ArrayList<Object[]>(snode.getValue().size());
475             for (Node subnode : snode.getValue()) {
476                 if (!(subnode instanceof MappingNode)) {
477                     throw new ConstructorException("while constructingpairs", node.getStartMark(),
478                             "expected a mapping of length 1, but found " + subnode.getNodeId(),
479                             subnode.getStartMark());
480                 }
481                 MappingNode mnode = (MappingNode) subnode;
482                 if (mnode.getValue().size() != 1) {
483                     throw new ConstructorException("while constructing pairs", node.getStartMark(),
484                             "expected a single mapping item, but found " + mnode.getValue().size()
485                                     + " items",
486                             mnode.getStartMark());
487                 }
488                 Node keyNode = mnode.getValue().get(0).getKeyNode();
489                 Node valueNode = mnode.getValue().get(0).getValueNode();
490                 Object key = constructObject(keyNode);
491                 Object value = constructObject(valueNode);
492                 pairs.add(new Object[] { key, value });
493             }
494             return pairs;
495         }
496     }
497
498     public class ConstructYamlSet implements Construct {
499         @Override
500         public Object construct(Node node) {
501             if (node.isTwoStepsConstruction()) {
502                 return (constructedObjects.containsKey(node) ? constructedObjects.get(node)
503                         : createDefaultSet(((MappingNode) node).getValue().size()));
504             } else {
505                 return constructSet((MappingNode) node);
506             }
507         }
508
509         @Override
510         @SuppressWarnings("unchecked")
511         public void construct2ndStep(Node node, Object object) {
512             if (node.isTwoStepsConstruction()) {
513                 constructSet2ndStep((MappingNode) node, (Set<Object>) object);
514             } else {
515                 throw new YAMLException("Unexpected recursive set structure. Node: " + node);
516             }
517         }
518     }
519
520     public class ConstructYamlStr extends AbstractConstruct {
521         @Override
522         public Object construct(Node node) {
523             return constructScalar((ScalarNode) node);
524         }
525     }
526
527     public class ConstructYamlSeq implements Construct {
528         @Override
529         public Object construct(Node node) {
530             SequenceNode seqNode = (SequenceNode) node;
531             if (node.isTwoStepsConstruction()) {
532                 return newList(seqNode);
533             } else {
534                 return constructSequence(seqNode);
535             }
536         }
537
538         @Override
539         @SuppressWarnings("unchecked")
540         public void construct2ndStep(Node node, Object data) {
541             if (node.isTwoStepsConstruction()) {
542                 constructSequenceStep2((SequenceNode) node, (List<Object>) data);
543             } else {
544                 throw new YAMLException("Unexpected recursive sequence structure. Node: " + node);
545             }
546         }
547     }
548
549     public class ConstructYamlMap implements Construct {
550         @Override
551         public Object construct(Node node) {
552             MappingNode mnode = (MappingNode) node;
553             if (node.isTwoStepsConstruction()) {
554                 return createDefaultMap(mnode.getValue().size());
555             } else {
556                 return constructMapping(mnode);
557             }
558         }
559
560         @Override
561         @SuppressWarnings("unchecked")
562         public void construct2ndStep(Node node, Object object) {
563             if (node.isTwoStepsConstruction()) {
564                 constructMapping2ndStep((MappingNode) node, (Map<Object, Object>) object);
565             } else {
566                 throw new YAMLException("Unexpected recursive mapping structure. Node: " + node);
567             }
568         }
569     }
570
571     public static final class ConstructUndefined extends AbstractConstruct {
572         @Override
573         public Object construct(Node node) {
574             throw new ConstructorException(nullnull,
575                     "could not determine a constructor for the tag " + node.getTag(),
576                     node.getStartMark());
577         }
578     }
579 }
580