You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@shindig.apache.org by jo...@apache.org on 2010/03/08 18:54:33 UTC

svn commit: r920421 - in /shindig/trunk/java/gadgets/src: main/java/org/apache/shindig/gadgets/render/ main/java/org/apache/shindig/gadgets/render/old/ main/java/org/apache/shindig/gadgets/rewrite/ main/java/org/apache/shindig/gadgets/templates/ main/j...

Author: johnh
Date: Mon Mar  8 17:54:32 2010
New Revision: 920421

URL: http://svn.apache.org/viewvc?rev=920421&view=rev
Log:
Same treatment; move all "old"-based (LinkRewriter-using, etc.) render/ classes to "old" package ahead of refactoring/reimpl.

The last remaining hook from non-"old" to "old" code is in template-handling code which uses SanitizingGadgetRewriter.bypassSanitization(elem, flag),
whose impl is the same between new and old versions, thereby still allowing the ability to switch between impls.


Added:
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/DefaultSanitizingProxyingLinkRewriterFactory.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriter.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriter.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriterFactory.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriter.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriterTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriterTest.java
Removed:
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/DefaultSanitizingProxyingLinkRewriterFactory.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriter.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingProxyingLinkRewriter.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingProxyingLinkRewriterFactory.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriter.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriterTest.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriterTest.java
Modified:
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderModule.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/RewriteModule.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/XmlTemplateLibrary.java
    shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/tags/FlashTagHandler.java
    shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderModule.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderModule.java?rev=920421&r1=920420&r2=920421&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderModule.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/RenderModule.java Mon Mar  8 17:54:32 2010
@@ -20,6 +20,8 @@
 
 import java.util.Set;
 
+import org.apache.shindig.gadgets.render.old.SanitizingGadgetRewriter;
+
 import com.google.common.collect.ImmutableSet;
 import com.google.inject.AbstractModule;
 import com.google.inject.TypeLiteral;

Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/DefaultSanitizingProxyingLinkRewriterFactory.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/DefaultSanitizingProxyingLinkRewriterFactory.java?rev=920421&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/DefaultSanitizingProxyingLinkRewriterFactory.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/DefaultSanitizingProxyingLinkRewriterFactory.java Mon Mar  8 17:54:32 2010
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.render.old;
+
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.rewrite.ContentRewriterFeature;
+import org.apache.shindig.gadgets.rewrite.old.ContentRewriterUris;
+
+@Singleton
+public class DefaultSanitizingProxyingLinkRewriterFactory implements
+    SanitizingProxyingLinkRewriterFactory {
+
+  private final ContentRewriterUris rewriterUris;
+
+  @Inject
+  public DefaultSanitizingProxyingLinkRewriterFactory(
+      ContentRewriterUris rewriterUris) {
+    this.rewriterUris = rewriterUris;
+  }
+
+  public SanitizingProxyingLinkRewriter create(Uri gadgetUri,
+      ContentRewriterFeature.Config rewriterFeature, String container,
+      String expectedMime, boolean debug, boolean nocache) {
+    return new SanitizingProxyingLinkRewriter(rewriterUris, gadgetUri,
+        rewriterFeature, container, expectedMime, debug, nocache);
+  }
+
+}

Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriter.java?rev=920421&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriter.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriter.java Mon Mar  8 17:54:32 2010
@@ -0,0 +1,445 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.render.old;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.parse.caja.CajaCssSanitizer;
+import org.apache.shindig.gadgets.rewrite.ContentRewriterFeature;
+import org.apache.shindig.gadgets.rewrite.GadgetRewriter;
+import org.apache.shindig.gadgets.rewrite.MutableContent;
+import org.apache.shindig.gadgets.rewrite.old.LinkRewriter;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.UserDataHandler;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableList;
+import com.google.inject.BindingAnnotation;
+import com.google.inject.Inject;
+
+/**
+ * A content rewriter that will sanitize output for simple 'badge' like display.
+ *
+ * This is intentionally not as robust as Caja. It is a simple element whitelist. It can not be used
+ * for sanitizing either javascript or CSS. CSS is desired in the long run, but it can't be proven
+ * safe in the short term.
+ *
+ * Generally used in conjunction with a gadget that gets its dynamic behavior externally (proxied
+ * rendering, OSML, etc.)
+ */
+public class SanitizingGadgetRewriter implements GadgetRewriter {
+
+  /** Key stored as element user-data to bypass sanitization */
+  private static final String BYPASS_SANITIZATION_KEY = "shindig.bypassSanitization";
+
+  /**
+   * Is the Gadget to be rendered sanitized?
+   * @return true if sanitization will be enabled
+   */
+  public static boolean isSanitizedRenderingRequest(Gadget gadget) {
+    return ("1".equals(gadget.getContext().getParameter("sanitize")));
+  }
+  
+  /**
+   * Marks that an element and all its attributes are trusted content.
+   * This status is preserved across {@link Node#cloneNode} calls.  Be
+   * extremely careful when using this, especially with {@code includingChildren}
+   * set to {@code true}, as untrusted content that gets inserted (e.g, via
+   * os:RenderAll in templating) would become trusted.
+   * 
+   * @param element the trusted element
+   * @param includingChildren if true, children of this element will are also
+   *     trusted.  Never set this to true on an element that will ever have
+   *     untrusted children inserted (e.g., if it contains or may contain os:Render).
+   */
+  public static void bypassSanitization(Element element, boolean includingChildren) {
+    element.setUserData(BYPASS_SANITIZATION_KEY,
+        includingChildren ? Bypass.ALL : Bypass.ONLY_SELF, copyOnClone);
+  }
+  
+  private static enum Bypass { ALL, ONLY_SELF, NONE }
+
+    private static UserDataHandler copyOnClone = new UserDataHandler() {
+    public void handle(short operation, String key, Object data, Node src, Node dst) {
+      if (operation == NODE_IMPORTED || operation == NODE_CLONED) {
+        dst.setUserData(key, data, copyOnClone);
+      }
+    }
+  };
+  
+  private final Set<String> allowedTags;
+  private final Set<String> allowedAttributes;
+  private final CajaCssSanitizer cssSanitizer;
+  private final ContentRewriterFeature.Factory rewriterFeatureFactory;
+  private final SanitizingProxyingLinkRewriterFactory sanitizingProxyingLinkRewriterFactory;
+
+  @Inject
+  public SanitizingGadgetRewriter(@AllowedTags Set<String> allowedTags,
+      @AllowedAttributes Set<String> allowedAttributes,
+      ContentRewriterFeature.Factory rewriterFeatureFactory,
+      CajaCssSanitizer cssSanitizer,
+      SanitizingProxyingLinkRewriterFactory sanitizingProxyingLinkRewriterFactory) {
+    this.allowedTags = allowedTags;
+    this.allowedAttributes = allowedAttributes;
+    this.cssSanitizer = cssSanitizer;
+    this.rewriterFeatureFactory = rewriterFeatureFactory;
+    this.sanitizingProxyingLinkRewriterFactory = sanitizingProxyingLinkRewriterFactory;
+  }
+
+
+  public void rewrite(Gadget gadget, MutableContent content) {
+    if (gadget.sanitizeOutput()) {
+      boolean sanitized = false;
+      try {
+        new NodeSanitizer(gadget).sanitize(content.getDocument().getDocumentElement());
+        content.documentChanged();
+        sanitized = true;
+      } finally {
+        // Defensively clean the content in case of failure
+        if (!sanitized) {
+          content.setContent("");
+        }
+      }
+    }
+  }
+
+  /**
+   * Utiliity class to sanitize HTML nodes recursively.
+   */
+  class NodeSanitizer {
+    private final Uri context;
+    private final List<DomFilter> filters;
+
+    NodeSanitizer(Gadget gadget) {
+      this.context = gadget.getSpec().getUrl();
+      Integer expires = rewriterFeatureFactory.getDefault().getExpires();
+      ContentRewriterFeature.Config rewriterFeature =
+          rewriterFeatureFactory.createRewriteAllFeature(expires == null ? -1 : expires);
+
+      SanitizingProxyingLinkRewriter cssImportRewriter = sanitizingProxyingLinkRewriterFactory
+          .create(gadget.getSpec().getUrl(), rewriterFeature, gadget
+              .getContext().getContainer(), "text/css", gadget.getContext()
+              .getDebug(), gadget.getContext().getIgnoreCache());
+      SanitizingProxyingLinkRewriter imageRewriter = sanitizingProxyingLinkRewriterFactory
+          .create(gadget.getSpec().getUrl(), rewriterFeature, gadget
+              .getContext().getContainer(), "image/*", gadget.getContext()
+              .getDebug(), gadget.getContext().getIgnoreCache());
+
+      // Create the set of filters to process in order.
+      filters = ImmutableList.of(
+        new BasicElementFilter(allowedTags, allowedAttributes),
+        new LinkSchemeCheckFilter(),
+        new StyleFilter(cssSanitizer, cssImportRewriter, imageRewriter),
+        new LinkFilter(cssImportRewriter),
+        new ImageFilter(imageRewriter),
+        new TargetFilter()
+      );
+    }
+
+    private void sanitize(Node node) {
+      switch (node.getNodeType()) {
+        case Node.CDATA_SECTION_NODE:
+        case Node.TEXT_NODE:
+        case Node.ENTITY_REFERENCE_NODE:
+          break;
+        case Node.ELEMENT_NODE:
+        case Node.DOCUMENT_NODE:
+          Element element = (Element) node;
+          Bypass bypass = canBypassSanitization(element);
+          if (bypass == Bypass.ALL) {
+            return;
+          } else if (bypass == Bypass.ONLY_SELF) {
+            for (Node child : toList(node.getChildNodes())) {
+              sanitize(child);
+            }
+          } else {
+            boolean removed = false;
+            for (DomFilter filter : filters) {
+              DomFilter.Result tagResult = filter.filterTag(element, context);
+              if (tagResult == DomFilter.Result.REMOVE) {
+                element.getParentNode().removeChild(element);
+                removed = true;
+                break;
+              } else if (tagResult == DomFilter.Result.CONTINUE) {
+                for (Attr attr : toList(node.getAttributes())) {
+                  DomFilter.Result attrResult = filter.filterAttribute(attr, context);
+                  if (attrResult == DomFilter.Result.PASS) {
+                    break; // No need to process more attributes
+                  } else if (attrResult == DomFilter.Result.REMOVE) {
+                    element.removeAttributeNode(attr);
+                  }
+                }
+              }
+            }
+            if (!removed) {
+              for (Node child : toList(node.getChildNodes())) {
+                sanitize(child);
+              }
+            }
+          }
+          break;
+        case Node.COMMENT_NODE:
+        default:
+          // Must remove all comments to avoid conditional comment evaluation.
+          // There might be other, unknown types as well. Don't trust them.
+          node.getParentNode().removeChild(node);
+          break;
+      }
+    }
+  }
+
+
+  /** Convert a NamedNodeMap to a list for easy and safe operations */
+  private static List<Attr> toList(NamedNodeMap nodes) {
+    List<Attr> list = new ArrayList<Attr>(nodes.getLength());
+
+    for (int i = 0, j = nodes.getLength(); i < j; ++i) {
+      list.add((Attr) nodes.item(i));
+    }
+
+    return list;
+  }
+
+  /** Convert a NamedNodeMap to a list for easy and safe operations */
+  private static List<Node> toList(NodeList nodes) {
+    List<Node> list = new ArrayList<Node>(nodes.getLength());
+
+    for (int i = 0, j = nodes.getLength(); i < j; ++i) {
+      list.add(nodes.item(i));
+    }
+
+    return list;
+  }
+
+  private static Bypass canBypassSanitization(Element element) {
+    Bypass bypass = (Bypass) element.getUserData(BYPASS_SANITIZATION_KEY);
+    if (bypass == null) {
+      bypass = Bypass.NONE;
+    }
+    return bypass;
+  }
+
+  /**
+   * Filter DOM elements and attributes to check their validity and
+   * restrict their allowed content
+   */
+  static interface DomFilter {
+    enum Result {
+      PASS,  // Check passed, do not process further checks on this filter
+      CONTINUE, // Check passed, process further checks on this filter
+      REMOVE // Check failed, remove item
+    }
+
+      /**
+     * Filter the element and possibly transform it.
+     */
+    Result filterTag(Element elem, Uri context);
+
+    /**
+     * Filter the attribute and possibly transform it
+\     */
+    Result filterAttribute(Attr attr, Uri context);
+  }
+
+  /**
+   * Restrict the set of allowed tags and attributes
+   */
+  static class BasicElementFilter implements DomFilter {
+    final Set<String> allowedTags;
+    final Set<String> allowedAttrs;
+
+    BasicElementFilter(Set<String> allowedTags, Set<String> allowedAttrs) {
+      this.allowedTags = allowedTags;
+      this.allowedAttrs = allowedAttrs;
+    }
+
+    public Result filterTag(Element elem, Uri context) {
+      return allowedTags.contains(elem.getNodeName().toLowerCase()) ?
+          Result.CONTINUE : Result.REMOVE;
+    }
+
+    public Result filterAttribute(Attr attr, Uri context) {
+      return allowedAttrs.contains(attr.getName().toLowerCase()) ?
+          Result.CONTINUE : Result.REMOVE;
+    }
+  }
+
+  /**
+   * Enfore that all uri's in the document have either http or https as
+   * their scheme
+   */
+  static class LinkSchemeCheckFilter implements DomFilter {
+    protected Set<String> uriAttributes;
+
+    LinkSchemeCheckFilter() {
+      uriAttributes = ImmutableSet.of("href", "src");
+    }
+
+    public Result filterTag(Element elem, Uri context) {
+      return Result.CONTINUE;
+    }
+
+    public Result filterAttribute(Attr attr, Uri context) {
+      if (uriAttributes.contains(attr.getName().toLowerCase())) {
+        try {
+          Uri uri = Uri.parse(attr.getValue());
+          String scheme = uri.getScheme();
+          if (scheme != null && !scheme.equals("http") && !scheme.equals("https")) {
+            return Result.REMOVE;
+          }
+        } catch (IllegalArgumentException iae) {
+          return Result.REMOVE;
+        }
+      }
+      return Result.CONTINUE;
+    }
+  }
+
+  /**
+   * Enfore that all images in the document are rewritten through the proxy.
+   * Prevents issues in IE where the image content contains script
+   */
+  static class ImageFilter implements DomFilter {
+    protected final LinkRewriter imageRewriter;
+
+    ImageFilter(LinkRewriter imageRewriter) {
+      this.imageRewriter = imageRewriter;
+    }
+
+    public Result filterTag(Element elem, Uri context) {
+      if ("img".equalsIgnoreCase(elem.getNodeName())) {
+        return Result.CONTINUE;
+      }
+      return Result.PASS;
+    }
+
+    public Result filterAttribute(Attr attr, Uri context) {
+      if ("src".equalsIgnoreCase(attr.getName())) {
+        attr.setValue(imageRewriter.rewrite(attr.getValue(), context));
+      }
+      return Result.PASS;
+    }
+  }
+
+  /**
+   * Pass the contents of style tags through the CSS sanitizer
+   */
+  static class StyleFilter implements DomFilter {
+    final CajaCssSanitizer sanitizer;
+    final LinkRewriter importRewriter;
+    final LinkRewriter imageRewriter;
+
+    StyleFilter(CajaCssSanitizer sanitizer, LinkRewriter importRewriter,
+        LinkRewriter imageRewriter) {
+      this.sanitizer = sanitizer;
+      this.importRewriter = importRewriter;
+      this.imageRewriter = imageRewriter;
+    }
+
+    public Result filterTag(Element elem, Uri context) {
+      if ("style".equalsIgnoreCase(elem.getNodeName())) {
+        sanitizer.sanitize(elem, context, importRewriter, imageRewriter);
+      }
+      return Result.PASS;
+    }
+
+    public Result filterAttribute(Attr attr, Uri context) {
+      return Result.PASS;
+    }
+  }
+
+  /**
+   * Restrict link tags to stylesheet content only and force the link to
+   * be rewritten through the proxy and sanitized
+   */
+  static class LinkFilter implements DomFilter {
+    final LinkRewriter rewriter;
+
+    LinkFilter(LinkRewriter rewriter) {
+      this.rewriter = rewriter;
+    }
+
+    public Result filterTag(Element elem, Uri context) {
+      if (!elem.getNodeName().equalsIgnoreCase("link")) {
+        return Result.CONTINUE;
+      }
+      boolean hasType = false;
+      for (Attr attr : toList(elem.getAttributes())) {
+        if ("rel".equalsIgnoreCase(attr.getName())) {
+          hasType |= "stylesheet".equalsIgnoreCase(attr.getValue());
+        } else if ("type".equalsIgnoreCase(attr.getName())) {
+          hasType |= "text/css".equalsIgnoreCase(attr.getValue());
+        } else if ("href".equalsIgnoreCase(attr.getName())) {
+          attr.setValue(rewriter.rewrite(attr.getValue(), context));
+        }
+      }
+      return hasType ? Result.PASS : Result.REMOVE;
+    }
+
+    public Result filterAttribute(Attr attr, Uri context) {
+      return Result.PASS;
+    }
+  }
+
+  /**
+   * Restrict the value of the target attribute on anchors etc. to
+   * _blank or _self or remove the node
+   */
+  static class TargetFilter implements DomFilter {
+
+    TargetFilter() {
+    }
+
+    public Result filterTag(Element elem, Uri context) {
+      return Result.CONTINUE;
+    }
+
+    public Result filterAttribute(Attr attr, Uri context) {
+      if ("target".equalsIgnoreCase(attr.getName())) {
+        String value = attr.getValue().toLowerCase();
+        if (!("_blank".equals(value) || "_self".equals(value))) {
+          return Result.REMOVE;
+        }
+      }
+      return Result.CONTINUE;
+    }
+  }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.PARAMETER)
+  @BindingAnnotation
+  public @interface AllowedTags { }
+
+  @Retention(RetentionPolicy.RUNTIME)
+  @Target(ElementType.PARAMETER)
+  @BindingAnnotation
+  public @interface AllowedAttributes { }
+}

Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriter.java?rev=920421&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriter.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriter.java Mon Mar  8 17:54:32 2010
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.shindig.gadgets.render.old;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.rewrite.ContentRewriterFeature;
+import org.apache.shindig.gadgets.rewrite.old.ContentRewriterUris;
+import org.apache.shindig.gadgets.rewrite.old.ProxyingLinkRewriter;
+import org.apache.shindig.gadgets.servlet.ProxyBase;
+
+/**
+ * Forcible rewrite the link through the proxy and force sanitization with
+ * an expected mime type
+ */
+public class SanitizingProxyingLinkRewriter extends ProxyingLinkRewriter {
+
+  private final String expectedMime;
+
+  public SanitizingProxyingLinkRewriter(ContentRewriterUris rewriterUris,
+      Uri gadgetUri, ContentRewriterFeature.Config rewriterFeature, String container,
+      String expectedMime, boolean debug, boolean nocache) {
+    super(rewriterUris, gadgetUri, rewriterFeature, container, debug, nocache);
+    this.expectedMime = expectedMime;
+  }
+
+  @Override
+  public String rewrite(String link, Uri context) {
+    try {
+      Uri.parse(link);
+    } catch (RuntimeException re) {
+      // Any failure in parse
+      return "about:blank";
+    }
+    String rewritten = super.rewrite(link, context);
+    rewritten += '&' + ProxyBase.SANITIZE_CONTENT_PARAM + "=1";
+    rewritten += '&' + ProxyBase.REWRITE_MIME_TYPE_PARAM + '=' + expectedMime;
+    return rewritten;
+  }
+}
\ No newline at end of file

Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriterFactory.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriterFactory.java?rev=920421&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriterFactory.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingProxyingLinkRewriterFactory.java Mon Mar  8 17:54:32 2010
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.render.old;
+
+import com.google.inject.ImplementedBy;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.rewrite.ContentRewriterFeature;
+
+@ImplementedBy(DefaultSanitizingProxyingLinkRewriterFactory.class)
+public interface SanitizingProxyingLinkRewriterFactory {
+  public SanitizingProxyingLinkRewriter create(Uri gadgetUri,
+      ContentRewriterFeature.Config rewriterFeature, String container,
+      String expectedMime, boolean debug, boolean nocache);
+}

Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriter.java?rev=920421&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriter.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriter.java Mon Mar  8 17:54:32 2010
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.render.old;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.sanselan.ImageFormat;
+import org.apache.sanselan.ImageReadException;
+import org.apache.sanselan.Sanselan;
+import org.apache.sanselan.common.byteSources.ByteSourceInputStream;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.parse.caja.CajaCssSanitizer;
+import org.apache.shindig.gadgets.rewrite.ContentRewriterFeature;
+import org.apache.shindig.gadgets.rewrite.MutableContent;
+import org.apache.shindig.gadgets.rewrite.RequestRewriter;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import com.google.inject.Inject;
+
+/**
+ * Rewriter that sanitizes CSS and image content.
+ */
+public class SanitizingRequestRewriter implements RequestRewriter {
+  private static final Logger logger =
+    Logger.getLogger(SanitizingRequestRewriter.class.getName());
+
+  private final CajaCssSanitizer cssSanitizer;
+  private final ContentRewriterFeature.Factory rewriterFeatureFactory;
+  private final SanitizingProxyingLinkRewriterFactory sanitizingProxyingLinkRewriterFactory;
+  
+  @Inject
+  public SanitizingRequestRewriter(
+      ContentRewriterFeature.Factory rewriterFeatureFactory,
+      CajaCssSanitizer cssSanitizer,
+      SanitizingProxyingLinkRewriterFactory sanitizingProxyingLinkRewriterFactory) {
+    this.cssSanitizer = cssSanitizer;
+    this.rewriterFeatureFactory = rewriterFeatureFactory;
+    this.sanitizingProxyingLinkRewriterFactory = sanitizingProxyingLinkRewriterFactory;
+  }
+
+  public boolean rewrite(HttpRequest request, HttpResponse resp, MutableContent content) {
+    // Content fetched through the proxy can stipulate that it must be sanitized.
+    if (request.isSanitizationRequested()) {
+      ContentRewriterFeature.Config rewriterFeature =
+        rewriterFeatureFactory.createRewriteAllFeature(request.getCacheTtl());
+      if (StringUtils.isEmpty(request.getRewriteMimeType())) {
+        logger.log(Level.WARNING, "Request to sanitize without content type for "
+            + request.getUri());
+        content.setContent("");
+        return true;
+      } else if (request.getRewriteMimeType().equalsIgnoreCase("text/css")) {
+        return rewriteProxiedCss(request, resp, content, rewriterFeature);
+      } else if (request.getRewriteMimeType().toLowerCase().startsWith("image/")) {
+        return rewriteProxiedImage(request, resp, content);
+      } else {
+        logger.log(Level.WARNING, "Request to sanitize unknown content type "
+            + request.getRewriteMimeType()
+            + " for " + request.getUri());
+        content.setContent("");
+        return true;
+      }
+    } else {
+      // No Op
+      return false;
+    }
+  }
+
+  /**
+   * We don't actually rewrite the image we just ensure that it is in fact a valid
+   * and known image type.
+   */
+  private boolean rewriteProxiedImage(HttpRequest request, HttpResponse resp,
+      MutableContent content) {
+    boolean imageIsSafe = false;
+    try {
+      String contentType = resp.getHeader("Content-Type");
+      if (contentType == null || contentType.toLowerCase().startsWith("image/")) {
+        // Unspecified or unknown image mime type.
+        try {
+          ImageFormat imageFormat = Sanselan
+          .guessFormat(new ByteSourceInputStream(resp.getResponse(),
+              request.getUri().getPath()));
+          if (imageFormat == ImageFormat.IMAGE_FORMAT_UNKNOWN) {
+            logger.log(Level.INFO, "Unable to sanitize unknown image type "
+                + request.getUri().toString());
+            return true;
+          }
+          imageIsSafe = true;
+          // Return false to indicate that no rewriting occurred
+          return false;
+        } catch (IOException ioe) {
+          throw new RuntimeException(ioe);
+        } catch (ImageReadException ire) {
+          // Unable to read the image so its not safe
+          logger.log(Level.INFO, "Unable to detect image type for " +request.getUri().toString() +
+              " for sanitized content", ire);
+          return true;
+        }
+      } else {
+        return true;
+      }
+    } finally {
+      if (!imageIsSafe) {
+        content.setContent("");
+      }
+    }
+  }
+
+  /**
+   * Sanitize a CSS file.
+   */
+  private boolean rewriteProxiedCss(HttpRequest request, HttpResponse response,
+      MutableContent content, ContentRewriterFeature.Config rewriterFeature) {
+    String sanitized = "";
+    try {
+      String contentType = response.getHeader("Content-Type");
+      if (contentType == null || contentType.toLowerCase().startsWith("text/")) {
+        SanitizingProxyingLinkRewriter cssImportRewriter = sanitizingProxyingLinkRewriterFactory
+            .create(request.getGadget(), rewriterFeature, request
+                .getContainer(), "text/css", false, request.getIgnoreCache());
+        SanitizingProxyingLinkRewriter cssImageRewriter = sanitizingProxyingLinkRewriterFactory
+            .create(request.getGadget(), rewriterFeature, request
+                .getContainer(), "image/*", false, request.getIgnoreCache());
+        sanitized = cssSanitizer.sanitize(content.getContent(), request.getUri(), cssImportRewriter,
+            cssImageRewriter);
+      }
+      
+      return true;
+    } finally {
+      // Set sanitized content in finally to ensure it is always cleared in
+      // the case of errors
+      content.setContent(sanitized);
+    }
+  }
+}

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/RewriteModule.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/RewriteModule.java?rev=920421&r1=920420&r2=920421&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/RewriteModule.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/RewriteModule.java Mon Mar  8 17:54:32 2010
@@ -20,8 +20,8 @@
 
 import org.apache.shindig.gadgets.render.OpenSocialI18NGadgetRewriter;
 import org.apache.shindig.gadgets.render.RenderingGadgetRewriter;
-import org.apache.shindig.gadgets.render.SanitizingGadgetRewriter;
-import org.apache.shindig.gadgets.render.SanitizingRequestRewriter;
+import org.apache.shindig.gadgets.render.old.SanitizingGadgetRewriter;
+import org.apache.shindig.gadgets.render.old.SanitizingRequestRewriter;
 import org.apache.shindig.gadgets.rewrite.old.CssRequestRewriter;
 import org.apache.shindig.gadgets.rewrite.old.HTMLContentRewriter;
 import org.apache.shindig.gadgets.servlet.CajaContentRewriter;

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java?rev=920421&r1=920420&r2=920421&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/TemplateRewriter.java Mon Mar  8 17:54:32 2010
@@ -27,7 +27,7 @@
 import org.apache.shindig.gadgets.GadgetException;
 import org.apache.shindig.gadgets.MessageBundleFactory;
 import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
-import org.apache.shindig.gadgets.render.SanitizingGadgetRewriter;
+import org.apache.shindig.gadgets.render.old.SanitizingGadgetRewriter;
 import org.apache.shindig.gadgets.spec.Feature;
 import org.apache.shindig.gadgets.spec.MessageBundle;
 import org.apache.shindig.gadgets.templates.ContainerTagLibraryFactory;

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/XmlTemplateLibrary.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/XmlTemplateLibrary.java?rev=920421&r1=920420&r2=920421&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/XmlTemplateLibrary.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/XmlTemplateLibrary.java Mon Mar  8 17:54:32 2010
@@ -21,7 +21,7 @@
 import org.apache.shindig.common.uri.Uri;
 import org.apache.shindig.common.xml.DomUtil;
 import org.apache.shindig.gadgets.GadgetException;
-import org.apache.shindig.gadgets.render.SanitizingGadgetRewriter;
+import org.apache.shindig.gadgets.render.old.SanitizingGadgetRewriter;
 import org.apache.shindig.gadgets.templates.tags.DefaultTagRegistry;
 import org.apache.shindig.gadgets.templates.tags.TagHandler;
 import org.apache.shindig.gadgets.templates.tags.TemplateBasedTagHandler;

Modified: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/tags/FlashTagHandler.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/tags/FlashTagHandler.java?rev=920421&r1=920420&r2=920421&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/tags/FlashTagHandler.java (original)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/templates/tags/FlashTagHandler.java Mon Mar  8 17:54:32 2010
@@ -31,7 +31,7 @@
 import org.apache.shindig.common.JsonSerializer;
 import org.apache.shindig.gadgets.features.FeatureRegistry;
 import org.apache.shindig.gadgets.features.FeatureResource;
-import org.apache.shindig.gadgets.render.SanitizingGadgetRewriter;
+import org.apache.shindig.gadgets.render.old.SanitizingGadgetRewriter;
 import org.apache.shindig.gadgets.templates.TemplateProcessor;
 import org.apache.shindig.protocol.conversion.BeanJsonConverter;
 import org.json.JSONObject;

Added: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriterTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriterTest.java?rev=920421&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriterTest.java (added)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingGadgetRewriterTest.java Mon Mar  8 17:54:32 2010
@@ -0,0 +1,418 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.render.old;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.Gadget;
+import org.apache.shindig.gadgets.GadgetContext;
+import org.apache.shindig.gadgets.parse.caja.CajaCssParser;
+import org.apache.shindig.gadgets.parse.caja.CajaCssSanitizer;
+import org.apache.shindig.gadgets.render.old.DefaultSanitizingProxyingLinkRewriterFactory;
+import org.apache.shindig.gadgets.render.old.SanitizingGadgetRewriter;
+import org.apache.shindig.gadgets.rewrite.BaseRewriterTestCase;
+import org.apache.shindig.gadgets.rewrite.ContentRewriterFeature;
+import org.apache.shindig.gadgets.rewrite.GadgetRewriter;
+import org.apache.shindig.gadgets.rewrite.MutableContent;
+import org.apache.shindig.gadgets.servlet.ProxyBase;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
+import static org.junit.Assert.assertEquals;
+import org.junit.Before;
+import org.junit.Test;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class SanitizingGadgetRewriterTest extends BaseRewriterTestCase {
+  private static final Set<String> DEFAULT_TAGS = ImmutableSet.of("html", "head", "body");
+  private static final Pattern BODY_REGEX = Pattern.compile(".*<body>(.+)</body>.*");
+
+  private final GadgetContext sanitaryGadgetContext = new GadgetContext() {
+    @Override
+    public String getParameter(String name) {
+      return ProxyBase.SANITIZE_CONTENT_PARAM.equals(name) ? "1" : null;
+    }
+    
+    @Override
+    public String getContainer() {
+      return MOCK_CONTAINER;
+    }
+  };
+
+  private final GadgetContext unsanitaryGadgetContext = new GadgetContext();
+  private final GadgetContext unsanitaryGadgetContextNoCacheAndDebug = new GadgetContext(){
+    @Override
+    public boolean getIgnoreCache() {
+      return true;
+    }
+    @Override
+    public boolean getDebug() {
+      return true;
+    }
+  };
+  private Gadget gadget;
+  private Gadget gadgetNoCacheAndDebug;
+
+  @Before
+  @Override
+  public void setUp() throws Exception {
+    super.setUp();
+    
+    gadget = new Gadget().setContext(unsanitaryGadgetContext);
+    gadget.setSpec(new GadgetSpec(Uri.parse("www.example.org/gadget.xml"),
+        "<Module><ModulePrefs title=''/><Content type='x-html-sanitized'/></Module>"));
+    gadget.setCurrentView(gadget.getSpec().getViews().values().iterator().next());
+
+    gadgetNoCacheAndDebug = new Gadget().setContext(unsanitaryGadgetContextNoCacheAndDebug);
+    gadgetNoCacheAndDebug.setSpec(new GadgetSpec(Uri.parse("www.example.org/gadget.xml"),
+        "<Module><ModulePrefs title=''/><Content type='x-html-sanitized'/></Module>"));
+    gadgetNoCacheAndDebug.setCurrentView(gadgetNoCacheAndDebug.getSpec().getViews().values().iterator().next());
+}
+
+  private String rewrite(Gadget gadget, String content, Set<String> tags, Set<String> attributes)
+      throws Exception {
+    GadgetRewriter rewriter = createRewriter(tags, attributes);
+
+    MutableContent mc = new MutableContent(parser, content);
+    rewriter.rewrite(gadget, mc);
+
+    Matcher matcher = BODY_REGEX.matcher(mc.getContent());
+    if (matcher.matches()) {
+      return matcher.group(1);
+    }
+    return mc.getContent();
+  }
+
+  private static Set<String> set(String... items) {
+    return Sets.newHashSet(items);
+  }
+
+  private GadgetRewriter createRewriter(Set<String> tags, Set<String> attributes) {
+    Set<String> newTags = new HashSet<String>(tags);
+    newTags.addAll(DEFAULT_TAGS);
+    ContentRewriterFeature.Factory rewriterFeatureFactory =
+        new ContentRewriterFeature.Factory(null,
+          new ContentRewriterFeature.DefaultConfig(".*", "", "HTTP", "embed,img,script,link,style", "false", "false"));
+    return new SanitizingGadgetRewriter(newTags, attributes, rewriterFeatureFactory,
+        new CajaCssSanitizer(new CajaCssParser()), new DefaultSanitizingProxyingLinkRewriterFactory(rewriterUris));
+  }
+
+  @Test
+  public void enforceTagWhiteList() throws Exception {
+    String markup =
+        "<p><style type=\"text/css\">A { font : bold }</style>text <b>bold text</b></p>" +
+        "<b>Bold text</b><i>Italic text<b>Bold text</b></i>";
+
+    String sanitized = "<p>text <b>bold text</b></p><b>Bold text</b>";
+
+    assertEquals(sanitized, rewrite(gadget, markup, set("p", "b"), set()));
+  }
+
+  @Test
+  public void enforceStyleSanitized() throws Exception {
+    String markup =
+        "<p><style type=\"text/css\">A { font : bold; behavior : bad }</style>text <b>bold text</b></p>" +
+        "<b>Bold text</b><i>Italic text<b>Bold text</b></i>";
+
+    String sanitized = "<html><head></head><body><p><style>A {\n  font: bold\n}</style>text " +
+        "<b>bold text</b></p><b>Bold text</b></body></html>";
+    assertEquals(sanitized, rewrite(gadget, markup, set("p", "b", "style"), set()));
+  }
+
+  @Test
+  public void enforceStyleLinkRewritten() throws Exception {
+    String markup =
+        "<link rel=\"stylesheet\" "
+            + "href=\"http://www.test.com/dir/proxy?"
+            + "url=http%3A%2F%2Fwww.evil.com%2Fx.css&gadget=www.example.org%2Fgadget.xml&"
+            + "fp=45508rewriteMime=text/css\"/>";
+    String sanitized = 
+        "<html><head><link href=\"http://www.test.com/dir/proxy?"
+            + "url=http%3A%2F%2Fwww.evil.com%2Fx.css&gadget=www.example.org%2Fgadget.xml&"
+            + "fp=45508&sanitize=1&rewriteMime=text/css\" rel=\"stylesheet\"></head><body></body></html>";
+    String rewritten = rewrite(gadget, markup, set("link"), set("rel", "href"));
+    assertEquals(sanitized, rewritten);
+  }
+
+  @Test
+  public void enforceStyleLinkRewrittenNoCacheAndDebug() throws Exception {
+    String markup =
+        "<link rel=\"stylesheet\" "
+            + "href=\"http://www.test.com/dir/proxy?"
+            + "url=http%3A%2F%2Fwww.evil.com%2Fx.css&gadget=www.example.org%2Fgadget.xml&"
+            + "fp=45508rewriteMime=text/css\"/>";
+    String sanitized = 
+        "<html><head><link href=\"http://www.test.com/dir/proxy?"
+            + "url=http%3A%2F%2Fwww.evil.com%2Fx.css&gadget=www.example.org%2Fgadget.xml&"
+            + "fp=45508&debug=1&nocache=1&sanitize=1&rewriteMime=text/css\" rel=\"stylesheet\"></head><body></body></html>";
+    String rewritten = rewrite(gadgetNoCacheAndDebug, markup, set("link"), set("rel", "href"));
+    assertEquals(sanitized, rewritten);
+  }
+
+  @Test
+  public void enforceNonStyleLinkStripped() throws Exception {
+    String markup =
+        "<link rel=\"script\" "
+            + "href=\"www.exmaple.org/evil.js\"/>";
+    String rewritten = rewrite(gadget, markup, set("link"), set("rel", "href", "type"));
+    assertEquals("<html><head></head><body></body></html>", rewritten);
+  }
+
+  @Test
+  public void enforceNonStyleLinkStrippedNoCacheAndDebug() throws Exception {
+    String markup =
+        "<link rel=\"script\" "
+            + "href=\"www.exmaple.org/evil.js\"/>";
+    String rewritten = rewrite(gadgetNoCacheAndDebug, markup, set("link"), set("rel", "href", "type"));
+    assertEquals("<html><head></head><body></body></html>", rewritten);
+  }
+
+  @Test
+  public void enforceCssImportLinkRewritten() throws Exception {
+    String markup =
+        "<style type=\"text/css\">@import url('www.evil.com/x.js');</style>";
+    // The caja css sanitizer does *not* remove the initial colon in urls
+    // since this does not work in IE
+    String sanitized = 
+        "<html><head><style>"
+      + "@import url('http://www.test.com/dir/proxy?url=www.example.org%2F"
+      +	"www.evil.com%2Fx.js&gadget=www.example.org%2Fgadget.xml&"
+      +	"fp=45508&sanitize=1&rewriteMime=text%2Fcss');"
+      + "</style></head><body></body></html>";
+    String rewritten = rewrite(gadget, markup, set("style"), set());
+    assertEquals(sanitized, rewritten);
+  }
+
+  @Test
+  public void enforceCssImportLinkRewrittenNoCacheAndDebug() throws Exception {
+    String markup =
+        "<style type=\"text/css\">@import url('www.evil.com/x.js');</style>";
+    // The caja css sanitizer does *not* remove the initial colon in urls
+    // since this does not work in IE
+    String sanitized = 
+        "<html><head><style>"
+      + "@import url('http://www.test.com/dir/proxy?url=www.example.org%2F"
+      + "www.evil.com%2Fx.js&gadget=www.example.org%2Fgadget.xml&"
+      + "fp=45508&debug=1&nocache=1&sanitize=1&rewriteMime=text%2Fcss');"
+      + "</style></head><body></body></html>";
+    String rewritten = rewrite(gadgetNoCacheAndDebug, markup, set("style"), set());
+    assertEquals(sanitized, rewritten);
+  }
+
+  @Test
+  public void enforceCssImportBadLinkStripped() throws Exception {
+    String markup =
+        "<style type=\"text/css\">@import url('javascript:doevil()'); A { font : bold }</style>";
+    String sanitized = "<html><head><style>A {\n"
+        + "  font: bold\n"
+        + "}</style></head><body></body></html>";
+    assertEquals(sanitized, rewrite(gadget, markup, set("style"), set()));
+  }
+
+  @Test
+  public void enforceAttributeWhiteList() throws Exception {
+    String markup = "<p foo=\"bar\" bar=\"baz\">Paragraph</p>";
+    String sanitized = "<p bar=\"baz\">Paragraph</p>";
+    assertEquals(sanitized, rewrite(gadget, markup, set("p"), set("bar")));
+  }
+
+  @Test
+  public void enforceImageSrcProxied() throws Exception {
+    String markup = "<img src='http://www.evil.com/x.js'>Evil happens</img>";
+    String sanitized = "<img src=\"http://www.test.com/dir/proxy?url=http%3A%2F%2Fwww.evil.com%2Fx.js&gadget=www.example.org%2Fgadget.xml&fp=45508&sanitize=1&rewriteMime=image/*\">Evil happens";
+    assertEquals(sanitized, rewrite(gadget, markup, set("img"), set("src")));
+  }
+
+  @Test
+  public void enforceImageSrcProxiedNoCacheAndDebug() throws Exception {
+    String markup = "<img src='http://www.evil.com/x.js'>Evil happens</img>";
+    String sanitized = "<img src=\"http://www.test.com/dir/proxy?url=http%3A%2F%2Fwww.evil.com%2Fx.js&gadget=www.example.org%2Fgadget.xml&fp=45508&debug=1&nocache=1&sanitize=1&rewriteMime=image/*\">Evil happens";
+    assertEquals(sanitized, rewrite(gadgetNoCacheAndDebug, markup, set("img"), set("src")));
+  }
+
+  @Test
+  public void enforceBadImageUrlStripped() throws Exception {
+    String markup = "<img src='java\\ script:evil()'>Evil happens</img>";
+    String sanitized = "<img>Evil happens";
+    assertEquals(sanitized, rewrite(gadget, markup, set("img"), set("src")));
+  }
+
+  @Test
+  public void enforceTargetTopRestricted() throws Exception {
+    String markup = "<a href=\"http://www.example.com\" target=\"_top\">x</a>";
+    String sanitized = "<a href=\"http://www.example.com\">x</a>";
+    assertEquals(sanitized, rewrite(gadget, markup, set("a"), set("href", "target")));
+  }
+
+  @Test
+  public void enforceTargetSelfAllowed() throws Exception {
+    String markup = "<a href=\"http://www.example.com\" target=\"_self\">x</a>";
+    assertEquals(markup, rewrite(gadget, markup, set("a"), set("href", "target")));
+  }
+
+  @Test
+  public void enforceTargetBlankAllowed() throws Exception {
+    String markup = "<a href=\"http://www.example.com\" target=\"_BlAnK\">x</a>";
+    assertEquals(markup, rewrite(gadget, markup, set("a"), set("href", "target")));
+  }
+
+  @Test
+  public void sanitizationBypassAllowed() throws Exception {
+    String markup = "<p foo=\"bar\"><b>Parag</b><!--raph--></p>";
+    // Create a rewriter that would strip everything
+    GadgetRewriter rewriter = createRewriter(set(), set());
+
+    MutableContent mc = new MutableContent(parser, markup);
+    Document document = mc.getDocument();
+    // Force the content to get re-serialized
+    MutableContent.notifyEdit(document);
+    String fullMarkup = mc.getContent();
+    
+    Element paragraphTag = (Element) document.getElementsByTagName("p").item(0);
+    // Mark the paragraph tag element as trusted
+    SanitizingGadgetRewriter.bypassSanitization(paragraphTag, true);
+    rewriter.rewrite(gadget, mc);
+     
+    // The document should be unchanged
+    assertEquals(fullMarkup, mc.getContent());
+  }
+     
+  @Test
+  public void sanitizationBypassOnlySelf() throws Exception {
+    String markup = "<p foo=\"bar\"><b>Parag</b><!--raph--></p>";
+    // Create a rewriter that would strip everything
+    GadgetRewriter rewriter = createRewriter(set(), set());
+
+    MutableContent mc = new MutableContent(parser, markup);
+    Document document = mc.getDocument();
+    
+    Element paragraphTag = (Element) document.getElementsByTagName("p").item(0);
+    // Mark the paragraph tag element as trusted
+    SanitizingGadgetRewriter.bypassSanitization(paragraphTag, false);
+    rewriter.rewrite(gadget, mc);
+     
+    // The document should be unchanged
+    String content = mc.getContent();
+    Matcher matcher = BODY_REGEX.matcher(content);
+    matcher.matches();
+    assertEquals("<p foo=\"bar\"></p>", matcher.group(1));
+  }
+     
+  @Test
+  public void sanitizationBypassPreservedAcrossClone() throws Exception {
+    String markup = "<p foo=\"bar\"><b>Parag</b><!--raph--></p>";
+    // Create a rewriter that would strip everything
+    GadgetRewriter rewriter = createRewriter(set(), set());
+
+    MutableContent mc = new MutableContent(parser, markup);
+    Document document = mc.getDocument();
+    
+    Element paragraphTag = (Element) document.getElementsByTagName("p").item(0);
+    // Mark the paragraph tag element as trusted
+    SanitizingGadgetRewriter.bypassSanitization(paragraphTag, false);
+
+    // Now, clone the paragraph tag and replace the paragraph tag
+    Element cloned = (Element) paragraphTag.cloneNode(true);
+    paragraphTag.getParentNode().replaceChild(cloned, paragraphTag);
+
+    rewriter.rewrite(gadget, mc);
+     
+    // The document should be unchanged
+    String content = mc.getContent();
+    Matcher matcher = BODY_REGEX.matcher(content);
+    matcher.matches();
+    assertEquals("<p foo=\"bar\"></p>", matcher.group(1));
+  }
+     
+ @Test
+  public void restrictHrefAndSrcAttributes() throws Exception {
+    String markup =
+        "<element " +
+        "href=\"http://example.org/valid-href\" " +
+        "src=\"http://example.org/valid-src\"/> " +
+        "<element " +
+        "href=\"https://example.org/valid-href\" " +
+        "src=\"https://example.org/valid-src\"/> " +
+        "<element " +
+        "href=\"http-evil://example.org/valid-href\" " +
+        "src=\"http-evil://example.org/valid-src\"/> " +
+        "<element " +
+        "href=\"javascript:evil()\" " +
+        "src=\"javascript:evil()\" /> " +
+        "<element " +
+        "href=\"//example.org/valid-href\" " +
+        "src=\"//example.org/valid-src\"/>";
+
+    // TODO: This test is only valid when using a parser that converts empty tags to
+    // balanced tags. The default (Neko) parser does this, with special case logic for handling
+    // empty tags like br or link.
+    String sanitized =
+      "<element " +
+      "href=\"http://example.org/valid-href\" " +
+      "src=\"http://example.org/valid-src\"></element> " +
+      "<element " +
+      "href=\"https://example.org/valid-href\" " +
+      "src=\"https://example.org/valid-src\"></element> " +
+      "<element></element> " +
+      "<element></element> " +
+      "<element " +
+      "href=\"//example.org/valid-href\" " +
+      "src=\"//example.org/valid-src\"></element>";
+
+    assertEquals(sanitized, rewrite(gadget, markup, set("element"), set("href", "src")));
+  }
+
+  @Test
+  public void allCommentsStripped() throws Exception {
+    String markup = "<b>Hello, world</b><!--<b>evil</b>-->";
+    assertEquals("<b>Hello, world</b>", rewrite(gadget, markup, set("b"), set()));
+  }
+
+  @Test
+  public void doesNothingWhenNotSanitized() throws Exception {
+    String markup = "<script src=\"http://evil.org/evil\"></script> <b>hello</b>";
+    Gadget gadget = new Gadget().setContext(unsanitaryGadgetContext);
+    gadget.setSpec(new GadgetSpec(Uri.parse("www.example.org/gadget.xml"),
+        "<Module><ModulePrefs title=''/><Content type='html'/></Module>"));
+    gadget.setCurrentView(gadget.getSpec().getViews().values().iterator().next());
+    assertEquals(markup, rewrite(gadget, markup, set("b"), set()));
+  }
+
+  @Test
+  public void forceSanitizeUnsanitaryGadget() throws Exception {
+    String markup =
+        "<p><style type=\"text/css\">A { font : bold; behavior : bad }</style>text <b>bold text</b></p>" +
+        "<b>Bold text</b><i>Italic text<b>Bold text</b></i>";
+
+    String sanitized = "<html><head></head><body><p><style>A {\n  font: bold\n}</style>text " +
+        "<b>bold text</b></p><b>Bold text</b></body></html>";
+
+    Gadget gadget = new Gadget().setContext(sanitaryGadgetContext);
+    gadget.setSpec(new GadgetSpec(Uri.parse("www.example.org/gadget.xml"),
+        "<Module><ModulePrefs title=''/><Content type='html'/></Module>"));
+    gadget.setCurrentView(gadget.getSpec().getViews().values().iterator().next());
+    assertEquals(sanitized, rewrite(gadget, markup, set("p", "b", "style"), set()));
+  }
+}

Added: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriterTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriterTest.java?rev=920421&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriterTest.java (added)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/old/SanitizingRequestRewriterTest.java Mon Mar  8 17:54:32 2010
@@ -0,0 +1,150 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.shindig.gadgets.render.old;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.http.HttpResponseBuilder;
+import org.apache.shindig.gadgets.parse.caja.CajaCssParser;
+import org.apache.shindig.gadgets.parse.caja.CajaCssSanitizer;
+import org.apache.shindig.gadgets.render.old.DefaultSanitizingProxyingLinkRewriterFactory;
+import org.apache.shindig.gadgets.render.old.SanitizingRequestRewriter;
+import org.apache.shindig.gadgets.rewrite.BaseRewriterTestCase;
+import org.apache.shindig.gadgets.rewrite.ContentRewriterFeature;
+import org.apache.shindig.gadgets.rewrite.MutableContent;
+import org.apache.shindig.gadgets.rewrite.RequestRewriter;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class SanitizingRequestRewriterTest extends BaseRewriterTestCase {
+  private static final Uri CONTENT_URI = Uri.parse("www.example.org/content");
+
+  private String rewrite(HttpRequest request, HttpResponse response) throws Exception {
+    request.setSanitizationRequested(true);
+    RequestRewriter rewriter = createRewriter(Collections.<String>emptySet(),
+        Collections.<String>emptySet());
+
+    MutableContent mc = new MutableContent(parser, response);
+    if (!rewriter.rewrite(request, response, mc)) {
+      return null;
+    }
+    return mc.getContent();
+  }
+
+  private RequestRewriter createRewriter(Set<String> tags, Set<String> attributes) {
+    ContentRewriterFeature.Factory rewriterFeatureFactory =
+        new ContentRewriterFeature.Factory(null,
+          new ContentRewriterFeature.DefaultConfig(".*", "", "HTTP", "embed,img,script,link,style", "false", "false"));
+    return new SanitizingRequestRewriter(rewriterFeatureFactory,
+        new CajaCssSanitizer(new CajaCssParser()), new DefaultSanitizingProxyingLinkRewriterFactory(rewriterUris));
+  }
+
+  @Test
+  public void enforceInvalidProxedCssRejected() throws Exception {
+    HttpRequest req = new HttpRequest(CONTENT_URI);
+    req.setRewriteMimeType("text/css");
+    HttpResponse response = new HttpResponseBuilder().setResponseString("doEvil()").create();
+    String sanitized = "";
+    assertEquals(sanitized, rewrite(req, response));
+  }
+
+  @Test
+  public void enforceValidProxedCssAccepted() throws Exception {
+    HttpRequest req = new HttpRequest(CONTENT_URI);
+    req.setRewriteMimeType("text/css");
+    HttpResponse response = new HttpResponseBuilder().setResponseString(
+        "@import url('http://www.evil.com/more.css'); A { font : BOLD }").create();
+    // The caja css sanitizer does *not* remove the initial colon in urls
+    // since this does not work in IE
+    String sanitized = 
+      "@import url('http://www.test.com/dir/proxy?"
+        + "url=http%3A%2F%2Fwww.evil.com%2Fmore.css"
+        + "&fp=45508&sanitize=1&rewriteMime=text%2Fcss');\n"
+        + "A {\n"
+        + "  font: BOLD\n"
+        + '}';
+    String rewritten = rewrite(req, response);
+    assertEquals(sanitized, rewritten);
+  }
+
+  @Test
+  public void enforceValidProxedCssAcceptedNoCache() throws Exception {
+    HttpRequest req = new HttpRequest(CONTENT_URI);
+    req.setRewriteMimeType("text/css");
+    req.setIgnoreCache(true);
+    HttpResponse response = new HttpResponseBuilder().setResponseString(
+        "@import url('http://www.evil.com/more.css'); A { font : BOLD }").create();
+    // The caja css sanitizer does *not* remove the initial colon in urls
+    // since this does not work in IE
+    String sanitized = 
+      "@import url('http://www.test.com/dir/proxy?"
+        + "url=http%3A%2F%2Fwww.evil.com%2Fmore.css"
+        + "&fp=45508&nocache=1&sanitize=1&rewriteMime=text%2Fcss');\n"
+        + "A {\n"
+        + "  font: BOLD\n"
+        + '}';
+    String rewritten = rewrite(req, response);
+    assertEquals(sanitized, rewritten);
+  }
+
+  @Test
+  public void enforceInvalidProxedImageRejected() throws Exception {
+    HttpRequest req = new HttpRequest(CONTENT_URI);
+    req.setRewriteMimeType("image/*");
+    HttpResponse response = new HttpResponseBuilder().setResponse("NOTIMAGE".getBytes()).create();
+    String sanitized = "";
+    assertEquals(sanitized, rewrite(req, response));
+  }
+
+  @Test
+  public void validProxiedImageAccepted() throws Exception {
+    HttpRequest req = new HttpRequest(CONTENT_URI);
+    req.setRewriteMimeType("image/*");
+    HttpResponse response = new HttpResponseBuilder().setResponse(
+        IOUtils.toByteArray(getClass().getClassLoader().getResourceAsStream(
+            "org/apache/shindig/gadgets/rewrite/image/inefficient.png"))).create();
+    assertNull(rewrite(req, response));
+  }
+
+  @Test
+  public void enforceUnknownMimeTypeRejected() throws Exception {
+    HttpRequest req = new HttpRequest(CONTENT_URI);
+    req.setRewriteMimeType("text/foo");
+    HttpResponse response = new HttpResponseBuilder().setResponseString("doEvil()").create();
+    String sanitized = "";
+    assertEquals(sanitized, rewrite(req, response));
+  }
+
+  @Test
+  public void enforceMissingMimeTypeRejected() throws Exception {
+    HttpRequest req = new HttpRequest(CONTENT_URI);
+    // A request without a mime type, but requesting sanitization, should be rejected
+    req.setRewriteMimeType(null);
+    HttpResponse response = new HttpResponseBuilder().setResponseString("doEvil()").create();
+    String sanitized = "";
+    assertEquals(sanitized, rewrite(req, response));
+  }
+}

Modified: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java?rev=920421&r1=920420&r2=920421&view=diff
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java (original)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/templates/DefaultTemplateProcessorTest.java Mon Mar  8 17:54:32 2010
@@ -27,7 +27,7 @@
 import org.apache.shindig.gadgets.parse.GadgetHtmlParser;
 import org.apache.shindig.gadgets.parse.ParseModule;
 import org.apache.shindig.gadgets.parse.nekohtml.NekoSimplifiedHtmlParser;
-import org.apache.shindig.gadgets.render.SanitizingGadgetRewriter;
+import org.apache.shindig.gadgets.render.old.SanitizingGadgetRewriter;
 import org.apache.shindig.gadgets.templates.tags.AbstractTagHandler;
 import org.apache.shindig.gadgets.templates.tags.DefaultTagRegistry;
 import org.apache.shindig.gadgets.templates.tags.TagHandler;