1
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
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
76 processDuplicateKeys(node);
77 if (node.isMerged()) {
78 node.setValue(mergeNode(node, true, new 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();
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
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
172 Object key = constructObject(keyNode);
173 if (!key2index.containsKey(key)) {
174 values.add(nodeTuple);
175
176 key2index.put(key, values.size() - 1);
177 } else if (isPreffered) {
178
179
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 for( int 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
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
381 calendar.set(Calendar.MONTH, Integer.parseInt(month_s) - 1);
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
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
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
412 timeZone = TimeZone.getTimeZone("UTC");
413 }
414 calendar = Calendar.getInstance(timeZone);
415 calendar.set(Calendar.YEAR, Integer.parseInt(year_s));
416
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
432
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
468
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(null, null,
575 "could not determine a constructor for the tag " + node.getTag(),
576 node.getStartMark());
577 }
578 }
579 }
580