1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.jexl3.introspection;
18
19 import static org.junit.jupiter.api.Assertions.assertEquals;
20 import static org.junit.jupiter.api.Assertions.assertNotNull;
21 import static org.junit.jupiter.api.Assertions.assertThrows;
22 import static org.junit.jupiter.api.Assertions.assertTrue;
23 import static org.junit.jupiter.api.Assertions.fail;
24
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Iterator;
28 import java.util.LinkedHashMap;
29 import java.util.List;
30 import java.util.Map;
31
32 import org.apache.commons.jexl3.JexlArithmetic;
33 import org.apache.commons.jexl3.JexlBuilder;
34 import org.apache.commons.jexl3.JexlContext;
35 import org.apache.commons.jexl3.JexlEngine;
36 import org.apache.commons.jexl3.JexlException;
37 import org.apache.commons.jexl3.JexlExpression;
38 import org.apache.commons.jexl3.JexlScript;
39 import org.apache.commons.jexl3.JexlTestCase;
40 import org.apache.commons.jexl3.MapContext;
41 import org.apache.commons.jexl3.annotations.NoJexl;
42 import org.apache.commons.logging.Log;
43 import org.apache.commons.logging.LogFactory;
44 import org.junit.jupiter.api.Test;
45
46
47
48
49 @SuppressWarnings({ "UnnecessaryBoxing", "AssertEqualsBetweenInconvertibleTypes" })
50 class SandboxTest extends JexlTestCase {
51 public abstract static class AbstractCallMeNot {
52 public @NoJexl String NONO = "should not be accessible!";
53
54 public String allowInherit() {
55 return "this is allowed";
56 }
57
58 @NoJexl
59 public void callMeNot() {
60 fail("should not be callable!");
61 }
62 }
63
64 public static class Arithmetic350 extends JexlArithmetic {
65
66 MapBuilder mb = new org.apache.commons.jexl3.internal.MapBuilder(3);
67
68 public Arithmetic350(final boolean astrict) {
69 super(astrict);
70 }
71
72 Map<?, ?> getLastMap() {
73 return (Map<Object, Object>) mb.create();
74 }
75
76 @Override
77 public MapBuilder mapBuilder(final int size, final boolean extended) {
78 return mb;
79 }
80 }
81
82 @NoJexl
83 public interface CantCallMe {
84 void tryMe();
85 }
86
87 public static class CantSeeMe {
88 public boolean doIt() {
89 return false;
90 }
91 }
92
93 public static class Foo extends AbstractCallMeNot implements CantCallMe, TryCallMe {
94 String name;
95 public String alias;
96
97 public Foo(final String name) {
98 this.name = name;
99 this.alias = name + "-alias";
100 }
101
102 public @NoJexl Foo(final String name, final String notcallable) {
103 throw new UnsupportedOperationException("should not be callable.");
104 }
105
106 @NoJexl
107 public String cantCallMe() {
108 throw new UnsupportedOperationException("should not be callable.");
109 }
110
111 public int doIt() {
112 return 42;
113 }
114
115 public String getName() {
116 return name;
117 }
118
119 public String Quux() {
120 return name + "-quux";
121 }
122
123 public void setName(final String name) {
124 this.name = name;
125 }
126
127 @Override
128 public void tryMe() {
129 throw new UnsupportedOperationException("should not be callable.");
130 }
131
132 @Override
133 public void tryMeARiver() {
134 throw new UnsupportedOperationException("should not be callable.");
135 }
136 }
137
138 public static class Foo386 implements SomeInterface {
139 @Override
140 public int bar() {
141 return 42;
142 }
143 }
144
145 public static class Foo42 {
146 public int getFoo() {
147 return 42;
148 }
149 }
150
151 public static class Foo43 extends Foo42 {
152 @Override
153 @NoJexl
154 public int getFoo() {
155 return 43;
156 }
157 }
158
159 public static class Foo44 extends Foo43 {
160 @Override
161 public int getFoo() {
162 return 44;
163 }
164 }
165
166 public abstract static class Operation {
167 protected final int base;
168
169 public Operation(final int sz) {
170 base = sz;
171 }
172
173 public abstract int nonCallable(int y);
174
175 public abstract int someOp(int x);
176 }
177
178 public static class Operation2 extends Operation {
179 public Operation2(final int sz) {
180 super(sz);
181 }
182
183 @Override
184 public int nonCallable(final int y) {
185 throw new UnsupportedOperationException("do NOT call");
186 }
187
188 @Override
189 public int someOp(final int x) {
190 return base + x;
191 }
192 }
193
194 public static class Quux386 extends Foo386 {
195 @Override
196 public int bar() {
197 return -42;
198 }
199 }
200
201 public interface SomeInterface {
202 int bar();
203 }
204
205 public interface TryCallMe {
206 @NoJexl
207 void tryMeARiver();
208 }
209
210
211 public static class Iterable386 implements Iterable<Integer> {
212 private final List<Integer> values = Arrays.asList(1, 2, 3);
213 @Override
214 public Iterator<Integer> iterator() {
215 return values.iterator();
216 }
217 }
218
219 static final Log LOGGER = LogFactory.getLog(SandboxTest.class.getName());
220
221 public SandboxTest() {
222 super("SandboxTest");
223 }
224
225 @Test
226 void testIteratorBlock() {
227
228 final String src = "var sum = 0; for (var i : it) { sum += i; } sum";
229 final Iterable386 it = new Iterable386();
230
231
232 final JexlSandbox open = new JexlSandbox();
233 final JexlEngine ojexl = new JexlBuilder().sandbox(open).strict(true).safe(false).create();
234 assertEquals(6, ojexl.createScript(src, "it").execute(null, it));
235
236
237 final JexlSandbox sandbox = new JexlSandbox();
238 sandbox.block(Iterable386.class.getName()).execute("iterator");
239 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
240 assertEquals(0, sjexl.createScript(src, "it").execute(null, it));
241 }
242
243 @Test
244 void testCantSeeMe() {
245 final JexlContext jc = new MapContext();
246 final String expr = "foo.doIt()";
247 JexlScript script;
248 Object result;
249
250 final JexlSandbox sandbox = new JexlSandbox(false);
251 sandbox.allow(Foo.class.getName());
252 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
253
254 jc.set("foo", new CantSeeMe());
255 script = sjexl.createScript(expr);
256 assertThrows(JexlException.class, () -> script.execute(jc));
257
258 jc.set("foo", new Foo("42"));
259 result = script.execute(jc);
260 assertEquals(42, ((Integer) result).intValue());
261 }
262
263 @Test
264 void testCtorAllow() {
265 final String expr = "new('" + Foo.class.getName() + "', '42')";
266 JexlScript script;
267 Object result;
268
269 final JexlSandbox sandbox = new JexlSandbox();
270 sandbox.allow(Foo.class.getName()).execute("");
271 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
272
273 script = sjexl.createScript(expr);
274 result = script.execute(null);
275 assertEquals("42", ((Foo) result).getName());
276 }
277
278 @Test
279 void testCtorBlock() {
280 final String expr = "new('" + Foo.class.getName() + "', '42')";
281 final JexlScript script = JEXL.createScript(expr);
282 Object result;
283 result = script.execute(null);
284 assertEquals("42", ((Foo) result).getName());
285
286 final JexlSandbox sandbox = new JexlSandbox();
287 sandbox.block(Foo.class.getName()).execute("");
288 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
289
290 final JexlScript script2 = sjexl.createScript(expr);
291 assertThrows(JexlException.Method.class, () -> script2.execute(null), "ctor should not be accessible");
292
293 }
294
295 @Test
296 void testGetAllow() {
297 final Foo foo = new Foo("42");
298 final String expr = "foo.alias";
299 JexlScript script;
300 Object result;
301
302 final JexlSandbox sandbox = new JexlSandbox();
303 sandbox.allow(Foo.class.getName()).read("alias");
304 sandbox.get(Foo.class.getName()).read().alias("alias", "ALIAS");
305 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
306
307 script = sjexl.createScript(expr, "foo");
308 result = script.execute(null, foo);
309 assertEquals(foo.alias, result);
310
311 script = sjexl.createScript("foo.ALIAS", "foo");
312 result = script.execute(null, foo);
313 assertEquals(foo.alias, result);
314 }
315
316 @Test
317 void testGetBlock() {
318 final String expr = "foo.alias";
319 final JexlScript script = JEXL.createScript(expr, "foo");
320 final Foo foo = new Foo("42");
321 Object result;
322 result = script.execute(null, foo);
323 assertEquals(foo.alias, result);
324
325 final JexlSandbox sandbox = new JexlSandbox();
326 sandbox.block(Foo.class.getName()).read("alias");
327 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
328
329 final JexlScript script2 = sjexl.createScript(expr, "foo");
330 assertThrows(JexlException.Property.class, () -> script2.execute(null, foo), "alias should not be accessible");
331 }
332
333 @Test
334 void testGetNullKeyAllowed0() {
335 final JexlEngine jexl = new JexlBuilder().sandbox(new JexlSandbox(true)).create();
336 final JexlExpression expression = jexl.createExpression("{null : 'foo'}[null]");
337 final Object o = expression.evaluate(null);
338 assertEquals("foo", o);
339 }
340
341 @Test
342 void testGetNullKeyAllowed1() {
343 final JexlSandbox sandbox = new JexlSandbox(true, true);
344 final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
345 p.read().add("quux");
346 final JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
347
348 final String q = "'quux'";
349 final JexlExpression expression = jexl.createExpression("{" + q + " : 'foo'}[" + q + "]");
350 assertTrue(assertThrows(JexlException.Property.class, () -> expression.evaluate(null), "should have blocked " + q).getMessage().contains("undefined"));
351
352
353 for (final String k : Arrays.asList("'foo'", "null")) {
354 final JexlExpression expression2 = jexl.createExpression("{" + k + " : 'foo'}[" + k + "]");
355 final Object o = expression2.evaluate(null);
356 assertEquals("foo", o);
357 }
358 }
359
360 @Test
361 void testGetNullKeyBlocked() {
362 final JexlSandbox sandbox = new JexlSandbox(true, true);
363 final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", false, true, true);
364 p.read().add(null);
365 p.read().add("quux");
366
367 final JexlEngine jexl = new JexlBuilder().sandbox(sandbox).create();
368 final JexlExpression e0 = jexl.createExpression("{'bar' : 'foo'}['bar']");
369 final Object r0 = e0.evaluate(null);
370 assertEquals("foo", r0);
371
372 for (final String k : Arrays.asList("'quux'", "null")) {
373 final JexlExpression expression = jexl.createExpression("{" + k + " : 'foo'}[" + k + "]");
374 assertTrue(
375 assertThrows(JexlException.Property.class, () -> expression.evaluate(null), "should have blocked " + k).getMessage().contains("undefined"));
376 }
377 }
378
379 @Test
380 void testInheritedPermission0() {
381 final Foo386 foo = new Foo386();
382 final JexlSandbox sandbox = new JexlSandbox(false, true);
383 sandbox.permissions(SomeInterface.class.getName(), true, true, true, true);
384 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
385 final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
386 assertEquals(42, (int) someOp.execute(null, foo));
387 }
388
389 @Test
390 void testInheritedPermission1() {
391 final Quux386 foo = new Quux386();
392 final JexlSandbox sandbox = new JexlSandbox(false, true);
393 sandbox.permissions(Foo386.class.getName(), true, true, true, true);
394 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
395 final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
396 assertEquals(-42, (int) someOp.execute(null, foo));
397 }
398
399 @Test
400 void testMethodAllow() {
401 final Foo foo = new Foo("42");
402 final String expr = "foo.Quux()";
403 JexlScript script;
404 Object result;
405
406 final JexlSandbox sandbox = new JexlSandbox();
407 sandbox.allow(Foo.class.getName()).execute("Quux");
408 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
409
410 script = sjexl.createScript(expr, "foo");
411 result = script.execute(null, foo);
412 assertEquals(foo.Quux(), result);
413 }
414
415 @Test
416 void testMethodBlock() {
417 final String expr = "foo.Quux()";
418 final JexlScript script = JEXL.createScript(expr, "foo");
419 final Foo foo = new Foo("42");
420 Object result;
421 result = script.execute(null, foo);
422 assertEquals(foo.Quux(), result);
423
424 final JexlSandbox sandbox = new JexlSandbox();
425 sandbox.block(Foo.class.getName()).execute("Quux");
426 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
427
428 final JexlScript script2 = sjexl.createScript(expr, "foo");
429 assertThrows(JexlException.Method.class, () -> script2.execute(null, foo), "Quux should not be accessible");
430 }
431
432 @Test
433 void testMethodNoJexl() {
434 final Foo foo = new Foo("42");
435
436 final String[] exprs = {
437 "foo.cantCallMe()",
438 "foo.tryMe()",
439 "foo.tryMeARiver()",
440 "foo.callMeNot()",
441 "foo.NONO",
442 "new('org.apache.commons.jexl3.SandboxTest$Foo', 'one', 'two')" };
443
444 final JexlEngine sjexl = new JexlBuilder().strict(true).safe(false).create();
445 for (final String expr : exprs) {
446 final JexlScript script = sjexl.createScript(expr, "foo");
447 assertThrows(JexlException.class, () -> script.execute(null, foo), "should have not been possible");
448 }
449 }
450
451 @Test
452 void testNoJexl312() {
453 final JexlContext ctxt = new MapContext();
454 final JexlEngine sjexl = new JexlBuilder().safe(false).strict(true).create();
455 final JexlScript foo = sjexl.createScript("x.getFoo()", "x");
456 assertThrows(JexlException.class, () -> foo.execute(ctxt, new Foo44()));
457 }
458
459 @Test
460 void testNonInheritedPermission0() {
461 final Foo386 foo = new Foo386();
462 final JexlSandbox sandbox = new JexlSandbox(false, true);
463 sandbox.permissions(SomeInterface.class.getName(), false, true, true, true);
464 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
465 final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
466 assertThrows(JexlException.class, () -> someOp.execute(null, foo), "should not be possible");
467 }
468
469 @Test
470 void testNonInheritedPermission1() {
471 final Quux386 foo = new Quux386();
472 final JexlSandbox sandbox = new JexlSandbox(false, true);
473 sandbox.permissions(Foo386.class.getName(), false, true, true, true);
474 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
475 final JexlScript someOp = sjexl.createScript("foo.bar()", "foo");
476 assertThrows(JexlException.class, () -> someOp.execute(null, foo), "should not be possible");
477 }
478
479 @Test
480 void testRestrict() {
481 final JexlContext context = new MapContext();
482 context.set("System", System.class);
483 final JexlSandbox sandbox = new JexlSandbox();
484
485 sandbox.allow(System.class.getName()).execute("currentTimeMillis");
486
487 sandbox.block(java.io.File.class.getName()).execute("");
488 final JexlEngine sjexl = new JexlBuilder().permissions(JexlPermissions.UNRESTRICTED).sandbox(sandbox).safe(false).strict(true).create();
489 Object result;
490 final JexlScript script1 = sjexl.createScript("System.exit()");
491 assertThrows(JexlException.class, () -> script1.execute(context), "should not allow calling exit.");
492 final JexlScript script2 = sjexl.createScript("System.exit(1)");
493 assertThrows(JexlException.class, () -> script2.execute(context), "should not allow calling exit.");
494 final JexlScript script3 = sjexl.createScript("new('java.io.File', '/tmp/should-not-be-created')");
495 assertThrows(JexlException.class, () -> script3.execute(context), "should not allow creating a file");
496 final JexlScript script4 = sjexl.createScript("System.currentTimeMillis()");
497 result = script4.execute(context);
498 assertNotNull(result);
499 }
500
501 @Test
502 void testSandboxInherit0() {
503 Object result;
504 final JexlContext ctxt = null;
505 final List<String> foo = new ArrayList<>();
506 final JexlSandbox sandbox = new JexlSandbox(false, true);
507 sandbox.allow(List.class.getName());
508
509 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
510 final JexlScript method = sjexl.createScript("foo.add(y)", "foo", "y");
511 final JexlScript set = sjexl.createScript("foo[x] = y", "foo", "x", "y");
512 final JexlScript get = sjexl.createScript("foo[x]", "foo", "x");
513
514 result = method.execute(ctxt, foo, "nothing");
515 assertTrue((boolean) result);
516 result = get.execute(null, foo, Integer.valueOf(0));
517 assertEquals("nothing", result);
518 result = set.execute(null, foo, Integer.valueOf(0), "42");
519 assertEquals("42", result);
520 result = get.execute(null, foo, Integer.valueOf(0));
521 assertEquals("42", result);
522 }
523
524 @Test
525 void testSandboxInherit1() {
526 Object result;
527 final JexlContext ctxt = null;
528 final Operation2 foo = new Operation2(12);
529 final JexlSandbox sandbox = new JexlSandbox(false, true);
530 sandbox.allow(Operation.class.getName());
531 sandbox.block(Operation.class.getName()).execute("nonCallable");
532
533 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
534 final JexlScript someOp = sjexl.createScript("foo.someOp(y)", "foo", "y");
535 result = someOp.execute(ctxt, foo, Integer.valueOf(30));
536 assertEquals(42, (int) result);
537 final JexlScript nonCallable = sjexl.createScript("foo.nonCallable(y)", "foo", "y");
538 assertThrows(JexlException.class, () -> nonCallable.execute(null, foo, Integer.valueOf(0)));
539 }
540
541 @Test
542 void testSetAllow() {
543 final Foo foo = new Foo("42");
544 final String expr = "foo.alias = $0";
545 JexlScript script;
546 Object result;
547
548 final JexlSandbox sandbox = new JexlSandbox();
549 sandbox.allow(Foo.class.getName()).write("alias");
550 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).safe(false).strict(true).create();
551
552 script = sjexl.createScript(expr, "foo", "$0");
553 result = script.execute(null, foo, "43");
554 assertEquals("43", result);
555 assertEquals("43", foo.alias);
556 }
557
558 @Test
559 void testSetBlock() {
560 final String expr = "foo.alias = $0";
561 final JexlScript script1 = JEXL.createScript(expr, "foo", "$0");
562 final Foo foo = new Foo("42");
563 Object result;
564 result = script1.execute(null, foo, "43");
565 assertEquals("43", result);
566
567 final JexlSandbox sandbox = new JexlSandbox();
568 sandbox.block(Foo.class.getName()).write("alias");
569 final JexlEngine sjexl = new JexlBuilder().sandbox(sandbox).strict(true).safe(false).create();
570
571 final JexlScript script2 = sjexl.createScript(expr, "foo", "$0");
572 assertThrows(JexlException.class, () -> script2.execute(null, foo, "43"), "alias should not be accessible");
573
574 }
575
576 @Test
577 void testSetNullKeyAllowed0() {
578 final Arithmetic350 a350 = new Arithmetic350(true);
579 final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(new JexlSandbox(true)).create();
580 final JexlContext jc = new MapContext();
581 final JexlExpression expression = jexl.createExpression("{null : 'foo'}[null] = 'bar'");
582 expression.evaluate(jc);
583 final Map<?, ?> map = a350.getLastMap();
584 assertEquals("bar", map.get(null));
585 }
586
587 @Test
588 void testSetNullKeyAllowed1() {
589 final Arithmetic350 a350 = new Arithmetic350(true);
590 final JexlSandbox sandbox = new JexlSandbox(true, true);
591 final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
592 p.write().add("quux");
593 final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
594
595 final String q = "'quux'";
596 final JexlExpression expression1 = jexl.createExpression("{" + q + " : 'foo'}[" + q + "] = '42'");
597 assertTrue(assertThrows(JexlException.Property.class, () -> expression1.evaluate(null), "should have blocked " + q).getMessage().contains("undefined"));
598
599 JexlExpression expression2 = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
600 expression2.evaluate(null);
601 Map<?, ?> map = a350.getLastMap();
602 assertEquals("42", map.get("bar"));
603 map.clear();
604 expression2 = jexl.createExpression("{null : 'foo'}[null] = '42'");
605 expression2.evaluate(null);
606 map = a350.getLastMap();
607 assertEquals("42", map.get(null));
608 }
609
610 @Test
611 void testSetNullKeyBlocked() {
612 final Arithmetic350 a350 = new Arithmetic350(true);
613 final JexlSandbox sandbox = new JexlSandbox(true, true);
614 final JexlSandbox.Permissions p = sandbox.permissions("java.util.Map", true, false, true);
615 p.write().add(null);
616 p.write().add("quux");
617 final JexlEngine jexl = new JexlBuilder().arithmetic(a350).sandbox(sandbox).create();
618
619 final JexlExpression expression = jexl.createExpression("{'bar' : 'foo'}['bar'] = '42'");
620 expression.evaluate(null);
621 final Map<?, ?> map = a350.getLastMap();
622 assertEquals("42", map.get("bar"));
623
624 for (final String k : Arrays.asList("'quux'", "null")) {
625 final JexlExpression expression2 = jexl.createExpression("{" + k + " : 'foo'}[" + k + "] = '42'");
626 assertTrue(assertThrows(JexlException.Property.class, () -> expression2.evaluate(null), "should have blocked " + k).getMessage()
627 .contains("undefined"));
628 }
629 }
630 interface I{}
631 static class A implements I{}
632 static class B extends A{}
633
634 @Test
635 void testPermissionOrder() {
636
637 final JexlSandbox sandboxAB = new JexlSandbox(false, true);
638 sandboxAB.permissions(I.class.getName(), true, true, true, false);
639 assertEquals("allow{all}", sandboxAB.get(A.class.getName()).write().toString());
640 assertEquals("allow{all}", sandboxAB.get(B.class.getName()).write().toString());
641 final JexlSandbox sandboxBA = new JexlSandbox(false, true);
642 sandboxBA.permissions(I.class.getName(), true, true, true, false);
643 assertEquals("allow{all}", sandboxBA.get(B.class.getName()).write().toString());
644 assertEquals("allow{all}", sandboxBA.get(A.class.getName()).write().toString());
645 }
646
647 @Test
648 void testIssue424() {
649 final JexlSandbox sandbox = new JexlSandbox(false, true);
650 sandbox.permissions(Map.class.getName(), true, true, true, true);
651 final String jexlCode = "x.foo = 'bar'";
652 final JexlEngine engine = new JexlBuilder()
653 .sandbox(sandbox)
654 .safe(false)
655 .strict(true).create();
656 final JexlContext context = new MapContext();
657 final Map<String, Object> x = new LinkedHashMap<>();
658 context.set("x", x);
659 final Object result = engine.createScript(jexlCode).execute(context);
660 assertEquals("bar", result);
661 assertEquals("bar", x.get("foo"));
662 }
663
664 }