BUG-2459: maintain a cache of grammar caches
[controller.git] / opendaylight / netconf / netconf-netty-util / src / main / java / org / opendaylight / controller / netconf / nettyutil / handler / NetconfEXICodec.java
index 98baef0f8580fb5ae8b2677ee20072ff14c3ab5c..16da7a7f9dcf3eabb7fee06e8c3201fce1c41155 100644 (file)
@@ -1,6 +1,9 @@
 package org.opendaylight.controller.netconf.nettyutil.handler;
 
 import com.google.common.base.Preconditions;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
 import org.openexi.proc.HeaderOptionsOutputType;
 import org.openexi.proc.common.EXIOptions;
 import org.openexi.proc.common.EXIOptionsException;
@@ -8,6 +11,9 @@ import org.openexi.proc.common.GrammarOptions;
 import org.openexi.proc.grammars.GrammarCache;
 import org.openexi.sax.EXIReader;
 import org.openexi.sax.Transmogrifier;
+import org.openexi.sax.TransmogrifierException;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
 
 public final class NetconfEXICodec {
     /**
@@ -16,13 +22,41 @@ public final class NetconfEXICodec {
      * of the stream. This is really useful, so let's output it now.
      */
     private static final boolean OUTPUT_EXI_COOKIE = true;
+    /**
+     * OpenEXI does not allow us to directly prevent resolution of external entities. In order
+     * to prevent XXE attacks, we reuse a single no-op entity resolver.
+     */
+    private static final EntityResolver ENTITY_RESOLVER = new EntityResolver() {
+        @Override
+        public InputSource resolveEntity(final String publicId, final String systemId) {
+            return new InputSource();
+        }
+    };
+
+    /**
+     * Since we have a limited number of options we can have, instantiating a weak cache
+     * will allow us to reuse instances where possible.
+     */
+    private static final LoadingCache<Short, GrammarCache> GRAMMAR_CACHES = CacheBuilder.newBuilder().weakValues().build(new CacheLoader<Short, GrammarCache>() {
+        @Override
+        public GrammarCache load(final Short key) {
+            return new GrammarCache(key);
+        }
+    });
+
+    /**
+     * Grammar cache acts as a template and is duplicated by the Transmogrifier and the Reader
+     * before use. It is safe to reuse a single instance.
+     */
+    private final GrammarCache exiGrammarCache;
     private final EXIOptions exiOptions;
 
     public NetconfEXICodec(final EXIOptions exiOptions) {
         this.exiOptions = Preconditions.checkNotNull(exiOptions);
+        this.exiGrammarCache = createGrammarCache(exiOptions);
     }
 
-    private GrammarCache getGrammarCache() {
+    private static GrammarCache createGrammarCache(final EXIOptions exiOptions) {
         short go = GrammarOptions.DEFAULT_OPTIONS;
         if (exiOptions.getPreserveComments()) {
             go = GrammarOptions.addCM(go);
@@ -37,23 +71,25 @@ public final class NetconfEXICodec {
             go = GrammarOptions.addPI(go);
         }
 
-        return new GrammarCache(null, go);
+        return GRAMMAR_CACHES.getUnchecked(go);
     }
 
     EXIReader getReader() throws EXIOptionsException {
         final EXIReader r = new EXIReader();
         r.setPreserveLexicalValues(exiOptions.getPreserveLexicalValues());
-        r.setGrammarCache(getGrammarCache());
+        r.setGrammarCache(exiGrammarCache);
+        r.setEntityResolver(ENTITY_RESOLVER);
         return r;
     }
 
-    Transmogrifier getTransmogrifier() throws EXIOptionsException {
+    Transmogrifier getTransmogrifier() throws EXIOptionsException, TransmogrifierException {
         final Transmogrifier transmogrifier = new Transmogrifier();
         transmogrifier.setAlignmentType(exiOptions.getAlignmentType());
         transmogrifier.setBlockSize(exiOptions.getBlockSize());
-        transmogrifier.setGrammarCache(getGrammarCache());
+        transmogrifier.setGrammarCache(exiGrammarCache);
         transmogrifier.setOutputCookie(OUTPUT_EXI_COOKIE);
         transmogrifier.setOutputOptions(HeaderOptionsOutputType.all);
+        transmogrifier.setResolveExternalGeneralEntities(false);
         return transmogrifier;
     }
 }