Author: doll
Date: Wed Apr 30 05:39:15 2008
New Revision: 652372

URL: http://svn.apache.org/viewvc?rev=652372&view=rev
Log:
SHINDIG-218
Patch from Brian Eaton:

If two gadgets can render on the same domain, they can see each other's data. 
This implements locked-domain for Shindig, using the same syntax that iGoogle 
uses: http://code.google.com/apis/gadgets/docs/reference.html#lockeddomain

The configuration in this patch disables locked-domain, since most people don't 
have wildcard DNS on their development machines.

Edit java/gadgets/conf/gadgets.properties to enable the locked domain feature.

Once you've changed that configuration, you can either enable locked domain for 
all gadgets on a particular container, or allow gadget authors to opt-in to the 
feature with <require feature="locked-domain"/> in their gadgets.

To enable locked-domain for a particular container, edit the container.js file 
to set gadgets.lockedDomainRequired = true.



Added:
    incubator/shindig/trunk/features/locked-domain/
    incubator/shindig/trunk/features/locked-domain/feature.xml
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Base32.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/StringEncoding.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/StringEncodingTest.java
Modified:
    incubator/shindig/trunk/config/container.js
    incubator/shindig/trunk/features/features.txt
    incubator/shindig/trunk/java/gadgets/conf/gadgets.properties
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingTask.java
    
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/GadgetRenderingTaskTest.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpTestFixture.java
    
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/ProxyHandlerTest.java

Modified: incubator/shindig/trunk/config/container.js
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/config/container.js?rev=652372&r1=652371&r2=652372&view=diff
==============================================================================
--- incubator/shindig/trunk/config/container.js (original)
+++ incubator/shindig/trunk/config/container.js Wed Apr 30 05:39:15 2008
@@ -43,6 +43,12 @@
 // value matching this set will return a 404 error.
 "gadgets.parent" : null,
 
+// Should all gadgets be forced on to a locked domain?
+"gadgets.lockedDomainRequired" : false,
+
+// DNS domain on which gadgets should render.
+"gadgets.lockedDomainSuffix" : "-a.example.com:8080",
+
 // This config data will be passed down to javascript. Please
 // configure your object using the feature name rather than
 // the javascript name.

Modified: incubator/shindig/trunk/features/features.txt
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/features/features.txt?rev=652372&r1=652371&r2=652372&view=diff
==============================================================================
--- incubator/shindig/trunk/features/features.txt (original)
+++ incubator/shindig/trunk/features/features.txt Wed Apr 30 05:39:15 2008
@@ -4,6 +4,7 @@
 features/core/feature.xml
 features/dynamic-height/feature.xml
 features/flash/feature.xml
+features/locked-domain/feature.xml
 features/minimessage/feature.xml
 features/opensocial-0.6/feature.xml
 features/opensocial-0.7/feature.xml

Added: incubator/shindig/trunk/features/locked-domain/feature.xml
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/features/locked-domain/feature.xml?rev=652372&view=auto
==============================================================================
--- incubator/shindig/trunk/features/locked-domain/feature.xml (added)
+++ incubator/shindig/trunk/features/locked-domain/feature.xml Wed Apr 30 
05:39:15 2008
@@ -0,0 +1,25 @@
+<?xml version="1.0"?>
+<!--
+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.
+-->
+<feature>
+<!--
+Required configuration:
+-->
+
+  <name>locked-domain</name>
+</feature>

Modified: incubator/shindig/trunk/java/gadgets/conf/gadgets.properties
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/conf/gadgets.properties?rev=652372&r1=652371&r2=652372&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/conf/gadgets.properties (original)
+++ incubator/shindig/trunk/java/gadgets/conf/gadgets.properties Wed Apr 30 
05:39:15 2008
@@ -5,5 +5,5 @@
 urls.js.prefix=/gadgets/js/
 signing.key-name=
 signing.key-file=
-
-
+locked-domain.enabled=false
+locked-domain.embed-host=127.0.0.1:8080

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java?rev=652372&r1=652371&r2=652372&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/DefaultGuiceModule.java
 Wed Apr 30 05:39:15 2008
@@ -64,6 +64,7 @@
 
     bind(GadgetBlacklist.class).to(BasicGadgetBlacklist.class);
     bind(Executor.class).toInstance(Executors.newCachedThreadPool());
+    bind(LockedDomainService.class).to(HashLockedDomainService.class);
 
     bind(ContainerConfig.class).in(Scopes.SINGLETON);
     bind(GadgetFeatureRegistry.class).in(Scopes.SINGLETON);

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java?rev=652372&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/HashLockedDomainService.java
 Wed Apr 30 05:39:15 2008
@@ -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;
+
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.shindig.gadgets.spec.Feature;
+import org.apache.shindig.util.Base32;
+
+import com.google.inject.Inject;
+import com.google.inject.name.Named;
+
+/**
+ * Locked domain implementation based on sha1.
+ * 
+ * The generated domain takes the form:
+ *
+ * base32(sha1(gadget url)).
+ * 
+ * Other domain locking schemes are possible as well.
+ */
+public class HashLockedDomainService implements LockedDomainService {
+  
+  private final ContainerConfig config;
+
+  private final String embedHost;
+
+  private final boolean enabled;
+  
+  private final Set<String> suffixes;
+  
+  private GadgetReader gadgetReader = new GadgetReader();
+  
+  public static final String LOCKED_DOMAIN_REQUIRED_KEY =
+      "gadgets.lockedDomainRequired";
+
+  public static final String LOCKED_DOMAIN_SUFFIX_KEY =
+      "gadgets.lockedDomainSuffix";
+  
+  /**
+   * Create a LockedDomainService
+   * @param config per-container configuration
+   * @param embedHost host name to use for embedded content
+   * @param enabled whether this service should do anything at all.
+   */
+  @Inject
+  public HashLockedDomainService(
+      ContainerConfig config,
+      @Named("locked-domain.embed-host")String embedHost,
+      @Named("locked-domain.enabled")boolean enabled) {
+    this.config = config;
+    this.embedHost = embedHost;
+    this.enabled = enabled;
+    suffixes = new HashSet<String>();
+    Set<String> containers = config.getContainers();
+    if (enabled) {
+      for (String container : containers) {
+        String suffix = config.get(container, LOCKED_DOMAIN_SUFFIX_KEY);
+        suffixes.add(suffix);
+      }
+    }
+  }
+
+  public String getEmbedHost() {
+    return embedHost;
+  }
+
+  public boolean embedCanRender(String host) {
+    return (!enabled || host.equals(embedHost));
+  }
+
+  public boolean gadgetCanRender(String host, Gadget gadget, String container) 
{
+    if (!enabled) {
+      return true;
+    }
+    // Gadgets can opt-in to locked domains, or they can be enabled globally
+    // for a particular container
+    if (gadgetReader.gadgetWantsLockedDomain(gadget) ||
+        containerWantsLockedDomain(container)) {
+      String neededHost = getLockedDomainForGadget(
+          gadgetReader.getGadgetUrl(gadget), container);
+      return (neededHost.equals(host));    
+    }
+    // Make sure gadgets that don't ask for locked domain aren't allowed
+    // to render on one.
+    return !gadgetUsingLockedDomain(host, gadget);
+  }
+  
+  // Simple class for dependency injection, so we don't need a full-fledged
+  // Gadget mock for these test cases
+  static class GadgetReader {
+    protected boolean gadgetWantsLockedDomain(Gadget gadget) {
+      Map<String, Feature> prefs =
+        gadget.getSpec().getModulePrefs().getFeatures();
+      return prefs.containsKey("locked-domain");      
+    }
+    
+    protected String getGadgetUrl(Gadget gadget) {
+      return gadget.getContext().getUrl().toString();
+    }
+  }
+  
+  // For testing only
+  void setSpecReader(GadgetReader gadgetReader) {
+    this.gadgetReader = gadgetReader;
+  }
+  
+  private boolean containerWantsLockedDomain(String container) {
+    String required = config.get(
+        container, LOCKED_DOMAIN_REQUIRED_KEY);
+    return ("true".equals(required));
+  }
+  
+  private boolean gadgetUsingLockedDomain(String host, Gadget gadget) {
+    for (String suffix : suffixes) {
+      if (host.endsWith(suffix)) {
+        return true;
+      }
+    }
+    return false;
+  }
+  
+  public String getLockedDomainForGadget(String gadget, String container) {
+    String suffix = config.get(container, LOCKED_DOMAIN_SUFFIX_KEY);
+    if (suffix == null) {
+      throw new IllegalStateException(
+          "Cannot redirect to locked domain if it is not configured");
+    }
+    byte[] sha1 = DigestUtils.sha(gadget);
+    String hash = new String(Base32.encodeBase32(sha1));
+    return hash + suffix;
+  }
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java?rev=652372&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/LockedDomainService.java
 Wed Apr 30 05:39:15 2008
@@ -0,0 +1,66 @@
+/*
+ * 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;
+
+/**
+ * Interface for locked domain, a security mechanism that ensures that
+ * a gadget is always registered on a fixed, unique domain. This prevents
+ * attacks from other gadgets that are rendered on the same domain, since all
+ * modern web browsers implement a same origin policy that prevents pages 
served
+ * from different hosts from accessing each other's data.
+ */
+public interface LockedDomainService {
+
+  /**
+   * Check whether embedded content (img src, for example) can render on
+   * a particular host.
+   * 
+   * @param host host name for rendered content
+   * @return true if the content should be allowed to render
+   */
+  public boolean embedCanRender(String host);
+  
+  /**
+   * Figure out where embedded content should render.
+   * 
+   * @return host name for safe rendering of embedded content.
+   */
+  public String getEmbedHost();
+  
+  /**
+   * Calculate the locked domain for a particular gadget on a particular
+   * container.
+   * 
+   * @param gadget URL of the gadget
+   * @param container name of the container page
+   * @return the host name on which the gadget should render
+   */
+  public String getLockedDomainForGadget(String gadget, String container);
+  
+  /**
+   * Check whether a gadget should be allowed to render on a particular
+   * host.
+   * 
+   * @param host host name for the content
+   * @param gadget URL of the gadget
+   * @param container container
+   * @return
+   */
+  public boolean gadgetCanRender(String host, Gadget gadget, String container);
+}

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingTask.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingTask.java?rev=652372&r1=652371&r2=652372&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingTask.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/GadgetRenderingTask.java
 Wed Apr 30 05:39:15 2008
@@ -28,6 +28,7 @@
 import org.apache.shindig.gadgets.GadgetServer;
 import org.apache.shindig.gadgets.GadgetTokenDecoder;
 import org.apache.shindig.gadgets.JsLibrary;
+import org.apache.shindig.gadgets.LockedDomainService;
 import org.apache.shindig.gadgets.RemoteContent;
 import org.apache.shindig.gadgets.spec.Feature;
 import org.apache.shindig.gadgets.spec.LocaleSpec;
@@ -78,6 +79,8 @@
   private final GadgetTokenDecoder tokenDecoder;
   private GadgetContext context;
   private final List<GadgetContentFilter> filters;
+  private final LockedDomainService domainLocker;
+  private String container = null;
 
   /**
    * Processes a single rendering request and produces output html or errors.
@@ -151,6 +154,39 @@
     }
   }
 
+  /** 
+   * Redirect a type=html gadget to a locked domain if necessary.
+   * 
+   * @param gadget
+   * @return true if the request was handled, false if the request can proceed
+   * @throws IOException
+   * @throws GadgetException 
+   */
+  private boolean mustRedirectToLockedDomain(Gadget gadget)
+      throws IOException, GadgetException {
+    
+    String host = request.getHeader("Host");    
+    String container = context.getContainer();
+    if (domainLocker.gadgetCanRender(host, gadget, container)) {
+      return false;
+    }
+    
+    // Gadget tried to render on wrong domain.
+    String gadgetUrl = context.getUrl().toString();
+    String required = domainLocker.getLockedDomainForGadget(
+        gadgetUrl, container);
+    String redir =
+        request.getScheme() + "://" +
+        required +
+        request.getServletPath() + "?" + 
+        request.getQueryString();
+    logger.info("Redirecting gadget " + context.getUrl() + " from domain " + 
+        host + " to domain " + redir);
+    response.sendRedirect(redir);
+
+    return true;
+  }
+
   /**
    * Handles type=html gadget output.
    *
@@ -161,6 +197,10 @@
    */
   private void outputHtmlGadget(Gadget gadget, View view)
       throws IOException, GadgetException {
+    if (mustRedirectToLockedDomain(gadget)) {
+      return;
+    }
+    
     response.setContentType("text/html; charset=UTF-8");
     StringBuilder markup = new StringBuilder();
 
@@ -423,14 +463,12 @@
             .append(";\n");
   }
 
-  /**
-   * Validates that the parent parameter was acceptable.
-   *
-   * @return True if the parent parameter is valid for the current
-   *     container.
-   */
-  private boolean validateParent() {
-    String container = request.getParameter("container");
+  /** Gets the container for the current request. */
+  private String getContainerForRequest() {
+    if (container != null) {
+      return container;
+    }
+    container = request.getParameter("container");
     if (container == null) {
       // The parameter used to be called 'synd' FIXME: schedule removal
       container = request.getParameter("synd");
@@ -438,6 +476,17 @@
         container = ContainerConfig.DEFAULT_CONTAINER;
       }
     }
+    return container;
+  }
+  
+  /**
+   * Validates that the parent parameter was acceptable.
+   *
+   * @return True if the parent parameter is valid for the current
+   *     container.
+   */
+  private boolean validateParent() {
+    String container = getContainerForRequest();
 
     String parent = request.getParameter("parent");
 
@@ -474,13 +523,15 @@
                              GadgetFeatureRegistry registry,
                              ContainerConfig containerConfig,
                              UrlGenerator urlGenerator,
-                             GadgetTokenDecoder tokenDecoder) {
+                             GadgetTokenDecoder tokenDecoder,
+                             LockedDomainService lockedDomainService) {
 
     this.server = server;
     this.registry = registry;
     this.containerConfig = containerConfig;
     this.urlGenerator = urlGenerator;
     this.tokenDecoder = tokenDecoder;
+    this.domainLocker = lockedDomainService;
     filters = new LinkedList<GadgetContentFilter>();
   }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java?rev=652372&r1=652371&r2=652372&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/http/ProxyHandler.java
 Wed Apr 30 05:39:15 2008
@@ -18,22 +18,6 @@
  */
 package org.apache.shindig.gadgets.http;
 
-import org.apache.shindig.gadgets.ContentFetcher;
-import org.apache.shindig.gadgets.ContentFetcherFactory;
-import org.apache.shindig.gadgets.GadgetException;
-import org.apache.shindig.gadgets.GadgetToken;
-import org.apache.shindig.gadgets.GadgetTokenDecoder;
-import org.apache.shindig.gadgets.RemoteContent;
-import org.apache.shindig.gadgets.RemoteContentRequest;
-import org.apache.shindig.gadgets.oauth.OAuthRequestParams;
-import org.apache.shindig.gadgets.spec.Auth;
-import org.apache.shindig.gadgets.spec.Preload;
-import org.apache.shindig.util.InputStreamConsumer;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import com.google.inject.Inject;
-
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
@@ -47,10 +31,28 @@
 import java.util.Map;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.logging.Logger;
 
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.shindig.gadgets.ContentFetcher;
+import org.apache.shindig.gadgets.ContentFetcherFactory;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.GadgetToken;
+import org.apache.shindig.gadgets.GadgetTokenDecoder;
+import org.apache.shindig.gadgets.LockedDomainService;
+import org.apache.shindig.gadgets.RemoteContent;
+import org.apache.shindig.gadgets.RemoteContentRequest;
+import org.apache.shindig.gadgets.oauth.OAuthRequestParams;
+import org.apache.shindig.gadgets.spec.Auth;
+import org.apache.shindig.gadgets.spec.Preload;
+import org.apache.shindig.util.InputStreamConsumer;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.google.inject.Inject;
+
 public class ProxyHandler {
   public static final String UNPARSEABLE_CRUFT = "throw 1; < don't be evil' >";
   public static final String POST_DATA_PARAM = "postData";
@@ -60,6 +62,9 @@
   public static final String NOCACHE_PARAM = "nocache";
   public static final String URL_PARAM = "url";
   private static final String REFRESH_PARAM = "refresh";
+  
+  private static final Logger logger = 
+      Logger.getLogger(ProxyHandler.class.getPackage().getName());
 
 
   private final GadgetTokenDecoder gadgetTokenDecoder;
@@ -79,6 +84,7 @@
   // This isn't a final field because we want to support optional injection.
   // This is a limitation of Guice, but this workaround...works.
   private ContentFetcherFactory contentFetcherFactory;
+  private final LockedDomainService domainLocker;
 
   @Inject
   public void setContentFetcher(ContentFetcherFactory contentFetcherFactory) {
@@ -87,9 +93,11 @@
 
   @Inject
   public ProxyHandler(ContentFetcherFactory contentFetcherFactory,
-                      GadgetTokenDecoder gadgetTokenDecoder) {
+                      GadgetTokenDecoder gadgetTokenDecoder,
+                      LockedDomainService lockedDomainService) {
     this.contentFetcherFactory = contentFetcherFactory;
     this.gadgetTokenDecoder = gadgetTokenDecoder;
+    this.domainLocker = lockedDomainService;
   }
 
   /**
@@ -260,6 +268,17 @@
                     HttpServletResponse response)
       throws IOException, GadgetException {
 
+    String host = request.getHeader("Host");
+    if (!domainLocker.embedCanRender(host)) {
+      // Force embedded images and the like to their own domain to avoid XSS
+      // in gadget domains.
+      String msg = "Embed request for url " +
+          getParameter(request, URL_PARAM, "") +
+          " made to wrong domain " + host;
+      logger.info(msg);
+      throw new GadgetException(GadgetException.Code.INVALID_PARAMETER, msg);
+    }
+    
     if (request.getHeader("If-Modified-Since") != null) {
       response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
       return;

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Base32.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Base32.java?rev=652372&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Base32.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/Base32.java
 Wed Apr 30 05:39:15 2008
@@ -0,0 +1,65 @@
+/*
+ * 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.util;
+
+import org.apache.commons.codec.BinaryDecoder;
+import org.apache.commons.codec.BinaryEncoder;
+import org.apache.commons.codec.DecoderException;
+import org.apache.commons.codec.EncoderException;
+
+/**
+ * Implements Base32 encoding.
+ */
+public class Base32 implements BinaryDecoder, BinaryEncoder {
+
+  private static final StringEncoding ENCODER =
+      new StringEncoding("0123456789abcdefghijklmnopqrstuv".toCharArray());
+
+  public static byte[] encodeBase32(byte[] arg0) {
+    return ENCODER.encode(arg0).getBytes(); 
+  }
+  
+  public static byte[] decodeBase32(byte[] arg0) {
+    return ENCODER.decode(new String(arg0)); 
+  }
+  
+  @SuppressWarnings("unused")
+  public byte[] decode(byte[] arg0) throws DecoderException {
+    return decodeBase32(arg0);
+  }
+
+  @SuppressWarnings("unused")
+  public byte[] encode(byte[] arg0) throws EncoderException {
+    return encodeBase32(arg0);
+  }
+
+  public Object decode(Object object) throws DecoderException {
+    if (!(object instanceof byte[])) {
+      throw new DecoderException(
+          "Parameter supplied to Base32 decode is not a byte[]");
+    }
+    return decodeBase32((byte[]) object);
+  }
+
+  public Object encode(Object object) throws EncoderException {
+    if (!(object instanceof byte[])) {
+      throw new EncoderException(
+          "Parameter supplied to Base64 encode is not a byte[]");
+    }
+    return encodeBase32((byte[]) object);
+  }
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/StringEncoding.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/StringEncoding.java?rev=652372&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/StringEncoding.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/util/StringEncoding.java
 Wed Apr 30 05:39:15 2008
@@ -0,0 +1,103 @@
+/*
+ * 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.util;
+
+import java.util.Arrays;
+import java.util.TreeSet;
+
+/**
+ * Utility class for encoding strings to and from byte arrays.
+ */
+public class StringEncoding {
+  private final char[] DIGITS;
+  private final int SHIFT;
+  private final int MASK;
+
+  /** Creates a new encoding based on the supplied set of digits. */
+  public StringEncoding(final char[] userDigits) {
+    TreeSet<Character> t = new TreeSet<Character>();
+    for (char c : userDigits) {
+      t.add(c);
+    }
+    char[] digits = new char[t.size()];
+    int i = 0;
+    for (char c : t) {
+      digits[i++] = c;
+    }
+    this.DIGITS = digits;
+    this.MASK = digits.length - 1;
+    this.SHIFT = Integer.numberOfTrailingZeros(MASK+1);
+    if ((MASK+1) != (1<<SHIFT) || digits.length >= 256) {
+      throw new AssertionError(Arrays.toString(digits));
+    }
+  }
+  
+  /** Returns the given bytes in their encoded form. */
+  public String encode(byte[] data) {
+    if (data.length == 0) {
+      return "";
+    }
+    StringBuilder result =
+      new StringBuilder(1 + data.length * 8 / DIGITS.length);
+    int buffer = data[0];
+    int next = 1;
+    int bitsLeft = 8;
+    while (bitsLeft > 0 || next < data.length) {
+      if (bitsLeft < SHIFT) {
+        if (next < data.length) {
+          buffer <<= 8;
+          buffer |= (data[next++] & 0xff);
+          bitsLeft += 8;
+        } else {
+          int pad = SHIFT - bitsLeft;
+          buffer <<= pad;
+          bitsLeft += pad;
+        }
+      }
+      int index = MASK & (buffer >> (bitsLeft - SHIFT));
+      bitsLeft -= SHIFT;
+      result.append(DIGITS[index]);
+    }
+    return result.toString();
+  }
+  
+  /** Decodes the given encoded string and returns the original raw bytes. */
+  public byte[] decode(String encoded) {
+    if (encoded.length() == 0) {
+      return new byte[0];
+    }
+    int encodedLength = encoded.length();
+    int outLength = encodedLength * SHIFT / 8;
+    byte[] result = new byte[outLength];
+    int buffer = 0;
+    int next = 0;
+    int bitsLeft = 0;
+    for (char c : encoded.toCharArray()) {
+      buffer <<= SHIFT;
+      buffer |= Arrays.binarySearch(DIGITS, c) & MASK;
+      bitsLeft += SHIFT;
+      if (bitsLeft >= 8) {
+        result[next++] = (byte) (buffer >> (bitsLeft - 8));
+        bitsLeft -= 8;
+      }
+    }
+    assert next == outLength && bitsLeft < SHIFT;
+    return result;
+  }  
+}

Added: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java?rev=652372&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/HashLockedDomainServiceTest.java
 Wed Apr 30 05:39:15 2008
@@ -0,0 +1,190 @@
+/*
+ * 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;
+
+import org.json.JSONArray;
+import org.json.JSONObject;
+import org.apache.shindig.gadgets.HashLockedDomainService.GadgetReader;
+
+public class HashLockedDomainServiceTest extends EasyMockTestCase {
+
+  HashLockedDomainService domainLocker;
+  Gadget gadget;
+  FakeSpecReader wantsLocked = new FakeSpecReader(
+      true, "http://somehost.com/somegadget.xml";);
+  FakeSpecReader noLocked = new FakeSpecReader(
+      false, "http://somehost.com/somegadget.xml";);
+  ContainerConfig containerEnabledConfig;
+  ContainerConfig containerRequiredConfig;
+  
+  /**
+   * Mocked out spec reader, rather than mocking the whole
+   * Gadget object.
+   */
+  public static class FakeSpecReader extends GadgetReader {
+    private boolean wantsLockedDomain;
+    private String gadgetUrl;
+    
+    public FakeSpecReader(boolean wantsLockedDomain, String gadgetUrl) {
+      this.wantsLockedDomain = wantsLockedDomain;
+      this.gadgetUrl = gadgetUrl;
+    }
+    
+    @Override
+    protected boolean gadgetWantsLockedDomain(Gadget gadget) {
+      return wantsLockedDomain;
+    }
+    
+    @Override
+    protected String getGadgetUrl(Gadget gadget) {
+      return gadgetUrl;
+    }
+  }
+  
+  @Override
+  protected void setUp() throws Exception {
+    super.setUp();
+    JSONObject json = new JSONObject();
+    json.put("gadgets.container",
+             new JSONArray().put(ContainerConfig.DEFAULT_CONTAINER));
+    json.put("gadgets.lockedDomainRequired", true);
+    json.put("gadgets.lockedDomainSuffix", "-a.example.com:8080");
+    containerRequiredConfig  = new ContainerConfig(null);
+    containerRequiredConfig.loadFromString(json.toString());
+    
+    json.put("gadgets.lockedDomainRequired", false);
+    containerEnabledConfig = new ContainerConfig(null);
+    containerEnabledConfig.loadFromString(json.toString());
+    gadget = mock(Gadget.class);
+  }
+
+  @Override
+  protected void tearDown() throws Exception {
+    super.tearDown();
+  }
+  
+  public void testDisabledGlobally() {
+    domainLocker = new HashLockedDomainService(
+        containerRequiredConfig, "embed.com", false);
+    assertTrue(domainLocker.embedCanRender("anywhere.com"));
+    assertTrue(domainLocker.embedCanRender("embed.com"));
+    assertTrue(domainLocker.gadgetCanRender("embed.com", gadget, "default"));
+    
+    domainLocker = new HashLockedDomainService(
+        containerEnabledConfig, "embed.com", false);
+    assertTrue(domainLocker.embedCanRender("anywhere.com"));
+    assertTrue(domainLocker.embedCanRender("embed.com"));
+    assertTrue(domainLocker.gadgetCanRender("embed.com", gadget, "default"));  
  
+  }
+  
+  public void testEnabledForGadget() {
+    domainLocker = new HashLockedDomainService(
+        containerEnabledConfig, "embed.com", true);
+    assertFalse(domainLocker.embedCanRender("anywhere.com"));
+    assertTrue(domainLocker.embedCanRender("embed.com"));
+    domainLocker.setSpecReader(wantsLocked);
+    assertFalse(domainLocker.gadgetCanRender(
+        "www.example.com", gadget, "default"));
+    assertTrue(domainLocker.gadgetCanRender(
+        "8uhr00296d2o3sfhqilj387krjmgjv3v-a.example.com:8080",
+        gadget,
+        "default"));
+    String target = domainLocker.getLockedDomainForGadget(
+        wantsLocked.getGadgetUrl(gadget), "default");
+    assertEquals(
+        "8uhr00296d2o3sfhqilj387krjmgjv3v-a.example.com:8080",
+        target);
+  }
+  
+  public void testNotEnabledForGadget() {
+    domainLocker = new HashLockedDomainService(
+        containerEnabledConfig, "embed.com", true);
+    domainLocker.setSpecReader(noLocked);
+    assertTrue(domainLocker.gadgetCanRender(
+        "www.example.com", gadget, "default"));
+    assertFalse(domainLocker.gadgetCanRender(
+        "8uhr00296d2o3sfhqilj387krjmgjv3v-a.example.com:8080",
+        gadget,
+        "default"));
+    assertFalse(domainLocker.gadgetCanRender(
+        "foo-a.example.com:8080",
+        gadget,
+        "default"));
+    assertFalse(domainLocker.gadgetCanRender(
+        "foo-a.example.com:8080",
+        gadget,
+        "othercontainer"));
+    String target = domainLocker.getLockedDomainForGadget(
+        wantsLocked.getGadgetUrl(gadget), "default");
+    assertEquals(
+        "8uhr00296d2o3sfhqilj387krjmgjv3v-a.example.com:8080",
+        target);    
+  }
+  
+  public void testRequiredForContainer() {
+    domainLocker = new HashLockedDomainService(
+        containerRequiredConfig, "embed.com", true);
+    domainLocker.setSpecReader(noLocked);
+    assertFalse(domainLocker.gadgetCanRender(
+        "www.example.com", gadget, "default"));
+    assertTrue(domainLocker.gadgetCanRender(
+        "8uhr00296d2o3sfhqilj387krjmgjv3v-a.example.com:8080",
+        gadget,
+        "default"));
+    String target = domainLocker.getLockedDomainForGadget(
+        wantsLocked.getGadgetUrl(gadget), "default");
+    assertEquals(
+        "8uhr00296d2o3sfhqilj387krjmgjv3v-a.example.com:8080",
+        target);
+  }
+  
+  public void testMissingConfig() throws Exception {
+    JSONObject json = new JSONObject();
+    json.put("gadgets.container",
+             new JSONArray().put(ContainerConfig.DEFAULT_CONTAINER));
+    ContainerConfig containerMissingConfig  = new ContainerConfig(null);
+    containerMissingConfig.loadFromString(json.toString());
+    domainLocker = new HashLockedDomainService(
+        containerMissingConfig, "embed.com", false);
+    domainLocker.setSpecReader(wantsLocked);
+    assertTrue(domainLocker.gadgetCanRender(
+        "www.example.com", gadget, "default"));
+  }
+  
+  public void testMultiContainer() throws Exception {
+    JSONObject json = new JSONObject();
+    json.put("gadgets.container",
+             new JSONArray()
+             .put(ContainerConfig.DEFAULT_CONTAINER)
+             .put("other"));
+    json.put("gadgets.lockedDomainRequired", true);
+    json.put("gadgets.lockedDomainSuffix", "-a.example.com:8080");
+    ContainerConfig inheritsConfig  = new ContainerConfig(null);
+    inheritsConfig.loadFromString(json.toString());
+    domainLocker = new HashLockedDomainService(
+        inheritsConfig, "embed.com", true);
+    domainLocker.setSpecReader(wantsLocked);
+    assertFalse(domainLocker.gadgetCanRender(
+        "www.example.com", gadget, "other"));
+    assertTrue(domainLocker.gadgetCanRender(
+        "8uhr00296d2o3sfhqilj387krjmgjv3v-a.example.com:8080",
+        gadget,
+        "other"));
+  }
+}

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/GadgetRenderingTaskTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/GadgetRenderingTaskTest.java?rev=652372&r1=652371&r2=652372&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/GadgetRenderingTaskTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/GadgetRenderingTaskTest.java
 Wed Apr 30 05:39:15 2008
@@ -19,10 +19,13 @@
 package org.apache.shindig.gadgets.http;
 
 import org.apache.shindig.gadgets.ContainerConfig;
+import org.apache.shindig.gadgets.Gadget;
 import org.apache.shindig.gadgets.GadgetContext;
 import org.apache.shindig.gadgets.RemoteContent;
 import org.apache.shindig.gadgets.RemoteContentRequest;
 import org.apache.shindig.gadgets.spec.GadgetSpec;
+import org.easymock.EasyMock;
+
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.isA;
 import org.json.JSONArray;
@@ -70,20 +73,41 @@
    * @throws Exception
    */
   private String parseBasicGadget(String view) throws Exception {
-
-    expect(request.getParameter("url")).andReturn(SPEC_URL.toString());
-    expect(request.getParameter("libs")).andReturn(LIBS);
-    expect(request.getParameter("view")).andReturn(view);
-    expect(request.getParameterNames()).andReturn(EMPTY_PARAMS);
-    expect(fetcher.fetch(SPEC_REQUEST)).andReturn(new RemoteContent(SPEC_XML));
-    expect(response.getWriter()).andReturn(writer);
+    expectParseRequestParams(view);
+    expectFetchGadget();
+    expectLockedDomainCheck();
+    expectWriteResponse();
     replay();
     gadgetRenderer.process(request, response);
     verify();
     writer.close();
     return new String(baos.toByteArray(), "UTF-8");
   }
-
+  
+  private void expectParseRequestParams(String view) throws Exception {
+    expect(request.getParameter("url")).andReturn(SPEC_URL.toString());
+    expect(request.getParameter("view")).andReturn(view);
+    expect(request.getParameterNames()).andReturn(EMPTY_PARAMS);
+    expect(request.getParameter("container")).andReturn(null);
+    expect(request.getHeader("Host")).andReturn("www.example.com");    
+  }
+  
+  private void expectLockedDomainCheck() throws Exception {
+    expect(lockedDomainService.gadgetCanRender(
+        EasyMock.eq("www.example.com"),
+        (Gadget)EasyMock.anyObject(),
+        EasyMock.eq("default"))).andReturn(true);    
+  }
+  
+  private void expectFetchGadget() throws Exception {
+    expect(fetcher.fetch(SPEC_REQUEST)).andReturn(new RemoteContent(SPEC_XML));
+  }
+  
+  private void expectWriteResponse() throws Exception {
+    expect(request.getParameter("libs")).andReturn(LIBS);
+    expect(response.getWriter()).andReturn(writer);    
+  }
+  
   public void testStandardsMode() throws Exception {
     String content = parseBasicGadget(GadgetSpec.DEFAULT_VIEW);
     assertTrue(-1 != content.indexOf(GadgetRenderingTask.STRICT_MODE_DOCTYPE));
@@ -125,6 +149,35 @@
 
     assertTrue(-1 != content.indexOf(ALT_CONTENT));
   }
+  
+  public void testLockedDomainFailure() throws Exception {
+    expectParseRequestParams(GadgetSpec.DEFAULT_VIEW);
+    expectFetchGadget();
+    expectLockedDomainFailure();
+    expectSendRedirect();
+    replay();
+    gadgetRenderer.process(request, response);
+    verify();
+    writer.close();
+  }
+
+  private void expectLockedDomainFailure() {
+    expect(lockedDomainService.gadgetCanRender(
+        EasyMock.eq("www.example.com"),
+        (Gadget)EasyMock.anyObject(),
+        EasyMock.eq("default"))).andReturn(false);
+    expect(request.getScheme()).andReturn("http");
+    expect(request.getServletPath()).andReturn("/gadgets/ifr");
+    expect(request.getQueryString()).andReturn("stuff=foo%20bar");
+    expect(lockedDomainService.getLockedDomainForGadget(
+        SPEC_URL.toString(), "default")).andReturn("locked.example.com");
+  }
+  
+  private void expectSendRedirect() throws Exception {
+    response.sendRedirect(
+        "http://locked.example.com/gadgets/ifr?stuff=foo%20bar";);
+    EasyMock.expectLastCall().once();
+  }
 
   // TODO: Lots of ugly tests on html content.
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpTestFixture.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpTestFixture.java?rev=652372&r1=652371&r2=652372&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpTestFixture.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/HttpTestFixture.java
 Wed Apr 30 05:39:15 2008
@@ -20,7 +20,7 @@
 
 import org.apache.shindig.gadgets.ContentFetcherFactory;
 import org.apache.shindig.gadgets.GadgetTestFixture;
-import org.apache.shindig.gadgets.GadgetTokenDecoder;
+import org.apache.shindig.gadgets.LockedDomainService;
 
 
 public abstract class HttpTestFixture extends GadgetTestFixture {
@@ -30,16 +30,17 @@
   public final ContentFetcherFactory contentFetcherFactory
       = mock(ContentFetcherFactory.class);
   public final UrlGenerator urlGenerator = mock(UrlGenerator.class);
-  public final GadgetTokenDecoder gadgetTokenDecoder
-      = mock(GadgetTokenDecoder.class);
+  public final LockedDomainService lockedDomainService =
+    mock(LockedDomainService.class);
 
   public HttpTestFixture() {
     super();
     proxyHandler = new ProxyHandler(
         contentFetcherFactory,
-        gadgetTokenDecoder);
+        gadgetTokenDecoder,
+        lockedDomainService);
     gadgetRenderer = new GadgetRenderingTask(gadgetServer, registry,
-        containerConfig, urlGenerator, gadgetTokenDecoder);
+        containerConfig, urlGenerator, gadgetTokenDecoder, 
lockedDomainService);
     jsonRpcHandler = new JsonRpcHandler(executor, gadgetServer, urlGenerator);
   }
 }

Modified: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/ProxyHandlerTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/ProxyHandlerTest.java?rev=652372&r1=652371&r2=652372&view=diff
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/ProxyHandlerTest.java
 (original)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/http/ProxyHandlerTest.java
 Wed Apr 30 05:39:15 2008
@@ -26,14 +26,19 @@
 import org.apache.shindig.gadgets.RemoteContentRequest;
 import org.apache.shindig.gadgets.spec.Auth;
 import org.apache.shindig.gadgets.spec.Preload;
+
 import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.isA;
 import org.json.JSONObject;
 
 import java.io.ByteArrayOutputStream;
+import java.io.IOException;
 import java.io.PrintWriter;
 import java.net.URI;
+import java.util.Enumeration;
+
+import javax.servlet.ServletOutputStream;
 
 public class ProxyHandlerTest extends HttpTestFixture {
 
@@ -44,7 +49,22 @@
 
   final ByteArrayOutputStream baos = new ByteArrayOutputStream();
   final PrintWriter writer = new PrintWriter(baos);
-
+  
+  final ServletOutputStream responseStream = new ServletOutputStream() {
+    @Override
+    public void write(int b) throws IOException {
+      baos.write(b); 
+    }
+  };
+  
+  final static Enumeration<String> EMPTY_LIST = new Enumeration<String>() {
+    public boolean hasMoreElements() {
+      return false;
+    }
+    public String nextElement() {
+      return null;
+    }
+  };
 
   private void expectGetAndReturnData(String url, byte[] data)
       throws Exception {
@@ -80,6 +100,19 @@
     expect(request.getParameter("url")).andReturn(url).atLeastOnce();
     expect(response.getWriter()).andReturn(writer).atLeastOnce();
   }
+  
+  private void setupProxyRequestMock(String host, String url) throws Exception 
{
+    expect(request.getMethod()).andReturn("GET").atLeastOnce();
+    expect(request.getHeader("Host")).andReturn(host);
+    expect(request.getParameter("url")).andReturn(url).atLeastOnce();
+    expect(request.getHeaderNames()).andReturn(EMPTY_LIST);
+    expect(response.getOutputStream()).andReturn(responseStream).atLeastOnce();
+  }
+  
+  private void setupFailedProxyRequestMock(String host, String url)
+      throws Exception {
+    expect(request.getHeader("Host")).andReturn(host);    
+  }
 
   private JSONObject readJSONResponse(String body) throws Exception {
     String json
@@ -99,6 +132,33 @@
     assertEquals(200, info.getInt("rc"));
     assertEquals(DATA_ONE, info.get("body"));
   }
+  
+  public void testLockedDomainEmbed() throws Exception {
+    setupProxyRequestMock("www.example.com", URL_ONE);
+    expect(lockedDomainService.embedCanRender("www.example.com"))
+        .andReturn(true);
+    expectGetAndReturnData(URL_ONE, DATA_ONE.getBytes());
+    replay();
+    proxyHandler.fetch(request, response);
+    verify();
+    responseStream.close();
+    assertEquals(DATA_ONE, new String(baos.toByteArray()));
+  }
+  
+  public void testLockedDomainFailedEmbed() throws Exception {
+    setupFailedProxyRequestMock("www.example.com", URL_ONE);
+    expect(lockedDomainService.embedCanRender("www.example.com"))
+        .andReturn(false);
+    replay();
+    try {
+      proxyHandler.fetch(request, response);
+      fail("should have thrown");
+    } catch (GadgetException e) {
+      assertTrue(
+          e.getMessage().indexOf("made to wrong domain www.example.com") != 
-1);
+    }
+    verify();
+  }
 
   public void testFetchDecodedUrl() throws Exception {
     String origUrl = "http://www.example.com";;

Added: 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/StringEncodingTest.java
URL: 
http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/StringEncodingTest.java?rev=652372&view=auto
==============================================================================
--- 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/StringEncodingTest.java
 (added)
+++ 
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/util/StringEncodingTest.java
 Wed Apr 30 05:39:15 2008
@@ -0,0 +1,58 @@
+/*
+ * 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.util;
+
+import static org.junit.Assert.*;
+
+import junit.framework.JUnit4TestAdapter;
+
+import org.junit.Test;
+
+public class StringEncodingTest {
+  public static junit.framework.Test suite() {
+    return new JUnit4TestAdapter(StringEncodingTest.class);
+  }
+ 
+  @Test
+  public void testBase32() throws Exception {
+    StringEncoding encoder = new StringEncoding(
+        "0123456789abcdefghijklmnopqrstuv".toCharArray()); 
+    testEncoding(encoder, new byte[] { 0 }, "00");
+    testEncoding(encoder, new byte[] { 0, 0 }, "0000");
+    testEncoding(encoder, new byte[] { 10, 0 }, "1800");
+    testRoundTrip(encoder, Crypto.getRandomBytes(1));
+    testRoundTrip(encoder, Crypto.getRandomBytes(2));
+    testRoundTrip(encoder, Crypto.getRandomBytes(3));
+    testRoundTrip(encoder, Crypto.getRandomBytes(20));
+    testRoundTrip(encoder, Crypto.getRandomBytes(30));
+  }
+
+  private void testRoundTrip(StringEncoding encoder, byte[] bytes) {
+    String encoded = encoder.encode(bytes);
+    byte[] decoded = encoder.decode(encoded);
+    assertArrayEquals(bytes, decoded);
+  }
+
+  private void testEncoding(StringEncoding encoder, byte[] b, String s) {
+    String encoded = encoder.encode(b);
+    assertEquals(s, encoded);
+    byte[] decoded = encoder.decode(encoded);
+    assertArrayEquals(b, decoded);
+  }
+}


Reply via email to