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/09 00:50:15 UTC
svn commit: r920573 - in /shindig/trunk/java/gadgets/src:
main/java/org/apache/shindig/gadgets/render/
test/java/org/apache/shindig/gadgets/render/
Author: johnh
Date: Mon Mar 8 23:50:15 2010
New Revision: 920573
URL: http://svn.apache.org/viewvc?rev=920573&view=rev
Log:
The existing Sanitizing rewriters, especially SanitizingGadgetRewriter, use both
LinkRewriter, which is being removed in favor of ProxyUriManager, as well as
implementing a custom Visitor pattern.
This CL:
A) removes the use of LinkRewriter in favor of the ProxyUriManager interface
B) removes the custom NodeVisitor implementation in favor of DomWalker
Added:
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/SanitizingProxyUriManager.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/SanitizingProxyUriManagerTest.java
shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriterTest.java
Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriter.java?rev=920573&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriter.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriter.java Mon Mar 8 23:50:15 2010
@@ -0,0 +1,394 @@
+/*
+ * 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;
+
+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.DomWalker;
+import org.apache.shindig.gadgets.rewrite.MutableContent;
+import org.apache.shindig.gadgets.rewrite.RewritingException;
+import org.apache.shindig.gadgets.uri.ProxyUriManager;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+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 extends DomWalker.Rewriter {
+
+ /** 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);
+ }
+ }
+ };
+
+ @Inject
+ public SanitizingGadgetRewriter(@AllowedTags Set<String> allowedTags,
+ @AllowedAttributes Set<String> allowedAttributes,
+ ContentRewriterFeature.Factory rewriterFeatureFactory,
+ CajaCssSanitizer cssSanitizer,
+ ProxyUriManager proxyUriManager) {
+ super(new BasicElementFilter(allowedTags, allowedAttributes),
+ new LinkSchemeCheckFilter(),
+ new StyleFilter(proxyUriManager, cssSanitizer),
+ new LinkFilter(proxyUriManager),
+ new ImageFilter(proxyUriManager),
+ new TargetFilter());
+ }
+
+
+ public void rewrite(Gadget gadget, MutableContent content) throws RewritingException {
+ if (gadget.sanitizeOutput()) {
+ boolean sanitized = false;
+ try {
+ super.rewrite(gadget, content);
+ sanitized = true;
+ } finally {
+ // Defensively clean the content in case of failure
+ if (!sanitized) {
+ content.setContent("");
+ }
+ }
+ }
+ }
+
+ /** 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;
+ }
+
+ private static Bypass canBypassSanitization(Element element) {
+ Bypass bypass = (Bypass) element.getUserData(BYPASS_SANITIZATION_KEY);
+ if (bypass == null) {
+ bypass = Bypass.NONE;
+ }
+ return bypass;
+ }
+
+ private static abstract class SanitizingWalker implements DomWalker.Visitor {
+ protected abstract boolean removeTag(Gadget gadget, Element elem, Uri ctx);
+ protected abstract boolean removeAttr(Gadget gadget, Attr attr, Uri ctx);
+
+ public VisitStatus visit(Gadget gadget, Node node) throws RewritingException {
+ Element elem = null;
+
+ switch (node.getNodeType()) {
+ case Node.CDATA_SECTION_NODE:
+ case Node.TEXT_NODE:
+ case Node.ENTITY_REFERENCE_NODE:
+ // Never modified.
+ return VisitStatus.BYPASS;
+ case Node.ELEMENT_NODE:
+ case Node.DOCUMENT_NODE:
+ // Continues through to follow-up logic.
+ elem = (Element)node;
+ 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.
+ return VisitStatus.RESERVE_TREE;
+ }
+
+ Bypass bypass = canBypassSanitization(elem);
+ if (bypass == Bypass.ALL) {
+ // This is double-checked in revisit below to ensure no modification/removal occurs.
+ return VisitStatus.RESERVE_TREE;
+ } else if (bypass == Bypass.ONLY_SELF) {
+ return VisitStatus.BYPASS;
+ }
+
+ if (removeTag(gadget, elem, gadget.getSpec().getUrl())) {
+ // All reserved trees are removed in revisit.
+ return VisitStatus.RESERVE_TREE;
+ }
+
+ // Otherwise move on to attributes.
+ VisitStatus status = VisitStatus.MODIFY;
+ for (Attr attr : toList(elem.getAttributes())) {
+ if (removeAttr(gadget, attr, gadget.getSpec().getUrl())) {
+ elem.removeAttributeNode(attr);
+ }
+ }
+
+ return status;
+ }
+
+ public boolean revisit(Gadget gadget, List<Node> nodes) throws RewritingException {
+ // Remove all reserved nodes, since these are all for which removeTag returned true.
+ for (Node node : nodes) {
+ if (node.getNodeType() == Node.COMMENT_NODE ||
+ canBypassSanitization((Element)node) != Bypass.ALL) {
+ node.getParentNode().removeChild(node);
+ }
+ }
+ return true;
+ }
+ }
+
+ /**
+ * Restrict the set of allowed tags and attributes
+ */
+ static class BasicElementFilter extends SanitizingWalker {
+ private final Set<String> allowedTags;
+ private final Set<String> allowedAttributes;
+
+ private BasicElementFilter(Set<String> allowedTags,
+ Set<String> allowedAttributes) {
+ this.allowedTags = allowedTags;
+ this.allowedAttributes = allowedAttributes;
+ }
+
+ public boolean removeTag(Gadget gadget, Element elem, Uri context) {
+ return !allowedTags.contains(elem.getNodeName().toLowerCase());
+ }
+
+ public boolean removeAttr(Gadget gadget, Attr attr, Uri context) {
+ return !allowedAttributes.contains(attr.getName().toLowerCase());
+ }
+ }
+
+ /**
+ * Enfore that all uri's in the document have either http or https as
+ * their scheme
+ */
+ static class LinkSchemeCheckFilter extends SanitizingWalker {
+ private static final Set<String> URI_ATTRIBUTES = ImmutableSet.of("href", "src");
+
+ @Override
+ protected boolean removeTag(Gadget gadget, Element elem, Uri ctx) {
+ return false;
+ }
+
+ @Override
+ protected boolean removeAttr(Gadget gadget, Attr attr, Uri ctx) {
+ if (URI_ATTRIBUTES.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 true;
+ }
+ } catch (IllegalArgumentException iae) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * 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 extends SanitizingWalker {
+ private final SanitizingProxyUriManager imageRewriter;
+
+ private ImageFilter(ProxyUriManager proxyUriManager) {
+ this.imageRewriter = new SanitizingProxyUriManager(proxyUriManager, "image/*");
+ }
+
+ @Override
+ protected boolean removeTag(Gadget gadget, Element elem, Uri ctx) {
+ return false;
+ }
+
+ @Override
+ protected boolean removeAttr(Gadget gadget, Attr attr, Uri ctx) {
+ if ("img".equalsIgnoreCase(attr.getOwnerElement().getNodeName()) &&
+ "src".equalsIgnoreCase(attr.getName())) {
+ try {
+ Uri uri = Uri.parse(attr.getValue());
+ attr.setValue(imageRewriter.make(
+ ProxyUriManager.ProxyUri.fromList(gadget, ImmutableList.of(uri)), null)
+ .get(0).toString());
+ } catch (IllegalArgumentException e) {
+ // Invalid Uri, remove.
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ /**
+ * Pass the contents of style tags through the CSS sanitizer
+ */
+ static class StyleFilter implements DomWalker.Visitor {
+ private final SanitizingProxyUriManager imageRewriter;
+ private final SanitizingProxyUriManager cssImportRewriter;
+ private final CajaCssSanitizer cssSanitizer;
+
+ private StyleFilter(ProxyUriManager proxyUriManager, CajaCssSanitizer cssSanitizer) {
+ this.imageRewriter = new SanitizingProxyUriManager(proxyUriManager, "image/*");
+ this.cssImportRewriter = new SanitizingProxyUriManager(proxyUriManager, "text/css");
+ this.cssSanitizer = cssSanitizer;
+ }
+
+ public VisitStatus visit(Gadget gadget, Node node) throws RewritingException {
+ if (node.getNodeType() == Node.ELEMENT_NODE &&
+ "style".equalsIgnoreCase(node.getNodeName())) {
+ cssSanitizer.sanitize(
+ (Element)node, gadget.getSpec().getUrl(), cssImportRewriter, imageRewriter);
+ return VisitStatus.MODIFY;
+ }
+ return VisitStatus.BYPASS;
+ }
+
+ public boolean revisit(Gadget gadget, List<Node> nodes) throws RewritingException {
+ return false;
+ }
+ }
+
+ /**
+ * Restrict link tags to stylesheet content only and force the link to
+ * be rewritten through the proxy and sanitized
+ */
+ static class LinkFilter extends SanitizingWalker {
+ private final SanitizingProxyUriManager cssImportRewriter;
+
+ private LinkFilter(ProxyUriManager proxyUriManager) {
+ this.cssImportRewriter = new SanitizingProxyUriManager(proxyUriManager, "text/css");
+ }
+
+ @Override
+ protected boolean removeTag(Gadget gadget, Element elem, Uri ctx) {
+ if (!elem.getNodeName().equalsIgnoreCase("link")) {
+ return false;
+ }
+ 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())) {
+ try {
+ attr.setValue(cssImportRewriter.make(
+ ProxyUriManager.ProxyUri.fromList(gadget,
+ ImmutableList.of(Uri.parse(attr.getValue()))), null).get(0).toString());
+ } catch (IllegalArgumentException e) {
+ return true;
+ }
+ }
+ }
+ return !hasType;
+ }
+
+ @Override
+ protected boolean removeAttr(Gadget gadget, Attr attr, Uri ctx) {
+ return false;
+ }
+ }
+
+ /**
+ * Restrict the value of the target attribute on anchors etc. to
+ * _blank or _self or remove the node
+ */
+ static class TargetFilter extends SanitizingWalker {
+ @Override
+ protected boolean removeTag(Gadget gadget, Element elem, Uri ctx) {
+ return false;
+ }
+
+ @Override
+ protected boolean removeAttr(Gadget gadget, Attr attr, Uri ctx) {
+ if ("target".equalsIgnoreCase(attr.getName())) {
+ String value = attr.getValue().toLowerCase();
+ if (!("_blank".equals(value) || "_self".equals(value))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+
+ @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/SanitizingProxyUriManager.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingProxyUriManager.java?rev=920573&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingProxyUriManager.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingProxyUriManager.java Mon Mar 8 23:50:15 2010
@@ -0,0 +1,64 @@
+/*
+ * 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;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.uri.UriBuilder;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.servlet.ProxyBase;
+import org.apache.shindig.gadgets.uri.ProxyUriManager;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * Forcible rewrite the link through the proxy and force sanitization with
+ * an expected mime type.
+ */
+public class SanitizingProxyUriManager implements ProxyUriManager {
+ private final ProxyUriManager wrapped;
+ private final String expectedMime;
+
+ public SanitizingProxyUriManager(ProxyUriManager wrapped, String expectedMime) {
+ this.wrapped = wrapped;
+ this.expectedMime = expectedMime;
+ }
+
+ public ProxyUri process(Uri uri) throws GadgetException {
+ return wrapped.process(uri);
+ }
+
+ public List<Uri> make(List<ProxyUri> ctx, Integer forcedRefresh) {
+ // Just wraps the original ProxyUriManager and adds a few query params.
+ List<Uri> origUris = wrapped.make(ctx, forcedRefresh);
+ List<Uri> sanitizedUris = Lists.newArrayListWithCapacity(origUris.size());
+
+ for (Uri origUri : origUris) {
+ UriBuilder newUri = new UriBuilder(origUri);
+ newUri.addQueryParameter(ProxyBase.SANITIZE_CONTENT_PARAM, "1");
+ if (expectedMime != null) {
+ newUri.addQueryParameter(ProxyBase.REWRITE_MIME_TYPE_PARAM, expectedMime);
+ }
+ sanitizedUris.add(newUri.toUri());
+ }
+
+ return sanitizedUris;
+ }
+}
\ No newline at end of file
Added: shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriter.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriter.java?rev=920573&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriter.java (added)
+++ shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriter.java Mon Mar 8 23:50:15 2010
@@ -0,0 +1,151 @@
+/*
+ * 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;
+
+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 org.apache.shindig.gadgets.uri.ProxyUriManager;
+
+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 ContentRewriterFeature.Factory featureConfigFactory;
+ private final CajaCssSanitizer cssSanitizer;
+ private final ProxyUriManager proxyUriManager;
+
+ @Inject
+ public SanitizingRequestRewriter(ContentRewriterFeature.Factory featureConfigFactory,
+ CajaCssSanitizer cssSanitizer,
+ ProxyUriManager proxyUriManager) {
+ this.featureConfigFactory = featureConfigFactory;
+ this.cssSanitizer = cssSanitizer;
+ this.proxyUriManager = proxyUriManager;
+ }
+
+ public boolean rewrite(HttpRequest request, HttpResponse resp, MutableContent content) {
+ // Content fetched through the proxy can stipulate that it must be sanitized.
+ if (request.isSanitizationRequested() &&
+ featureConfigFactory.get(request).shouldRewriteURL(request.getUri().toString())) {
+ 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);
+ } 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) {
+ String sanitized = "";
+ try {
+ String contentType = response.getHeader("Content-Type");
+ if (contentType == null || contentType.toLowerCase().startsWith("text/")) {
+ SanitizingProxyUriManager cssImageRewriter =
+ new SanitizingProxyUriManager(proxyUriManager, "image/*");
+ SanitizingProxyUriManager cssImportRewriter =
+ new SanitizingProxyUriManager(proxyUriManager, "text/css");
+ 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);
+ }
+ }
+}
Added: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriterTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriterTest.java?rev=920573&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriterTest.java (added)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingGadgetRewriterTest.java Mon Mar 8 23:50:15 2010
@@ -0,0 +1,420 @@
+/*
+ * 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;
+
+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.rewrite.RewriterTestBase;
+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 org.apache.shindig.gadgets.uri.PassthruManager;
+
+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 RewriterTestBase {
+ 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 PassthruManager("host.com", "/proxy"));
+ }
+
+ @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=45508&rewriteMime=text/css\"/>";
+ String sanitized =
+ "<html><head><link href=\"http://host.com/proxy?url=http%3A%2F%2Fwww.test.com%2Fdir%2F" +
+ "proxy%3Furl%3Dhttp%253A%252F%252Fwww.evil.com%252Fx.css%26gadget%3Dwww.example.org%252F" +
+ "gadget.xml%26fp%3D45508%26rewriteMime%3Dtext%2Fcss&sanitize=1&rewriteMime=text%2Fcss\" " +
+ "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=45508&rewriteMime=text/css\"/>";
+ String sanitized =
+ "<html><head><link href=\"http://host.com/proxy?url=http%3A%2F%2Fwww.test.com%2F"
+ + "dir%2Fproxy%3Furl%3Dhttp%253A%252F%252Fwww.evil.com%252Fx.css%26gadget%3D"
+ + "www.example.org%252Fgadget.xml%26fp%3D45508%26rewriteMime%3Dtext%2Fcss&"
+ + "sanitize=1&rewriteMime=text%2Fcss\" 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://host.com/proxy?url=www.example.org%2Fwww.evil.com%2Fx.js&"
+ + "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://host.com/proxy?url=www.example.org%2Fwww.evil.com%2Fx.js&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://host.com/proxy?url=http%3A%2F%2F" +
+ "www.evil.com%2Fx.js&sanitize=1&rewriteMime=image%2F*\">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://host.com/proxy?url=http%3A%2F%2Fwww.evil.com" +
+ "%2Fx.js&sanitize=1&rewriteMime=image%2F*\">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/SanitizingProxyUriManagerTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingProxyUriManagerTest.java?rev=920573&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingProxyUriManagerTest.java (added)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingProxyUriManagerTest.java Mon Mar 8 23:50:15 2010
@@ -0,0 +1,145 @@
+/*
+ * 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;
+
+import static org.easymock.classextension.EasyMock.createMock;
+import static org.easymock.classextension.EasyMock.capture;
+import static org.easymock.classextension.EasyMock.expect;
+import static org.easymock.classextension.EasyMock.replay;
+import static org.easymock.classextension.EasyMock.verify;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+
+import org.apache.shindig.common.uri.Uri;
+import org.apache.shindig.common.uri.UriBuilder;
+import org.apache.shindig.gadgets.servlet.ProxyBase;
+import org.apache.shindig.gadgets.uri.ProxyUriManager;
+import org.apache.shindig.gadgets.uri.ProxyUriManager.ProxyUri;
+import org.easymock.Capture;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+public class SanitizingProxyUriManagerTest {
+ private ProxyUriManager uriManager;
+ private Uri uri;
+ private ProxyUri proxyUri;
+
+ @Before
+ public void setUp() throws Exception {
+ uriManager = createMock(ProxyUriManager.class);
+ uri = new UriBuilder().setScheme("http").setAuthority("host.com").setPath("/path").toUri();
+ proxyUri = createMock(ProxyUri.class);
+ }
+
+ @Test
+ public void processPassesThrough() throws Exception {
+ Capture<Uri> uriCapture = new Capture<Uri>();
+ expect(uriManager.process(capture(uriCapture))).andReturn(proxyUri).once();
+ replay(uriManager);
+
+ SanitizingProxyUriManager rewriter = makeRewriter(null);
+ ProxyUri returned = rewriter.process(uri);
+
+ verify(uriManager);
+ assertSame(uri, uriCapture.getValue());
+ assertSame(returned, proxyUri);
+ }
+
+ @Test
+ public void makeSingleNoMime() throws Exception {
+ Capture<List<ProxyUri>> uriCapture = new Capture<List<ProxyUri>>();
+ Capture<Integer> intCapture = new Capture<Integer>();
+ List<ProxyUri> input = Lists.newArrayList(proxyUri);
+ List<Uri> output = Lists.newArrayList(uri);
+ Integer refresh = new Integer(0);
+ expect(uriManager.make(capture(uriCapture), capture(intCapture)))
+ .andReturn(output).once();
+ replay(uriManager);
+
+ SanitizingProxyUriManager rewriter = makeRewriter(null);
+ List<Uri> returned = rewriter.make(input, refresh);
+
+ verify(uriManager);
+ assertSame(uriCapture.getValue(), input);
+ assertSame(intCapture.getValue(), refresh);
+ assertEquals(1, returned.size());
+ assertEquals("1", returned.get(0).getQueryParameter(ProxyBase.SANITIZE_CONTENT_PARAM));
+ assertNull(returned.get(0).getQueryParameter(ProxyBase.REWRITE_MIME_TYPE_PARAM));
+ }
+
+ @Test
+ public void makeSingleExpectedMime() throws Exception {
+ Capture<List<ProxyUri>> uriCapture = new Capture<List<ProxyUri>>();
+ Capture<Integer> intCapture = new Capture<Integer>();
+ List<ProxyUri> input = Lists.newArrayList(proxyUri);
+ List<Uri> output = Lists.newArrayList(uri);
+ Integer refresh = new Integer(0);
+ String mime = "my/mime";
+ expect(uriManager.make(capture(uriCapture), capture(intCapture)))
+ .andReturn(output).once();
+ replay(uriManager);
+
+ SanitizingProxyUriManager rewriter = makeRewriter(mime);
+ List<Uri> returned = rewriter.make(input, refresh);
+
+ verify(uriManager);
+ assertSame(uriCapture.getValue(), input);
+ assertSame(intCapture.getValue(), refresh);
+ assertEquals(1, returned.size());
+ assertEquals("1", returned.get(0).getQueryParameter(ProxyBase.SANITIZE_CONTENT_PARAM));
+ assertEquals(mime, returned.get(0).getQueryParameter(ProxyBase.REWRITE_MIME_TYPE_PARAM));
+ }
+
+ @Test
+ public void makeList() throws Exception {
+ Capture<List<ProxyUri>> uriCapture = new Capture<List<ProxyUri>>();
+ Capture<Integer> intCapture = new Capture<Integer>();
+ List<ProxyUri> input = Lists.newArrayList(proxyUri);
+ Uri uri2 = new UriBuilder().toUri();
+ List<Uri> output = Lists.newArrayList(uri, uri2);
+ Integer refresh = new Integer(0);
+ String mime = "my/mime";
+ expect(uriManager.make(capture(uriCapture), capture(intCapture)))
+ .andReturn(output).once();
+ replay(uriManager);
+
+ SanitizingProxyUriManager rewriter = makeRewriter(mime);
+ List<Uri> returned = rewriter.make(input, refresh);
+
+ verify(uriManager);
+ assertSame(uriCapture.getValue(), input);
+ assertSame(intCapture.getValue(), refresh);
+ assertEquals(2, returned.size());
+ assertEquals("1", returned.get(0).getQueryParameter(ProxyBase.SANITIZE_CONTENT_PARAM));
+ assertEquals(mime, returned.get(0).getQueryParameter(ProxyBase.REWRITE_MIME_TYPE_PARAM));
+ assertEquals("1", returned.get(1).getQueryParameter(ProxyBase.SANITIZE_CONTENT_PARAM));
+ assertEquals(mime, returned.get(1).getQueryParameter(ProxyBase.REWRITE_MIME_TYPE_PARAM));
+ }
+
+ private SanitizingProxyUriManager makeRewriter(String mime) {
+ return new SanitizingProxyUriManager(uriManager, mime);
+ }
+}
Added: shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriterTest.java
URL: http://svn.apache.org/viewvc/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriterTest.java?rev=920573&view=auto
==============================================================================
--- shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriterTest.java (added)
+++ shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/render/SanitizingRequestRewriterTest.java Mon Mar 8 23:50:15 2010
@@ -0,0 +1,148 @@
+/*
+ * 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;
+
+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.rewrite.RewriterTestBase;
+import org.apache.shindig.gadgets.rewrite.ContentRewriterFeature;
+import org.apache.shindig.gadgets.rewrite.MutableContent;
+import org.apache.shindig.gadgets.rewrite.RequestRewriter;
+import org.apache.shindig.gadgets.uri.PassthruManager;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Set;
+
+public class SanitizingRequestRewriterTest extends RewriterTestBase {
+ 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 PassthruManager());
+ }
+
+ @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 =
+ // Resultant URL is just the "sanitized" version of same, since we're using
+ // PassthruUriManager for testing purposes.
+ "@import url('http://www.evil.com/more.css?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.evil.com/more.css?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));
+ }
+}