You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@shindig.apache.org by et...@apache.org on 2008/06/11 11:33:23 UTC
svn commit: r666593 [1/2] - in /incubator/shindig/trunk/java/gadgets/src:
main/java/org/apache/shindig/gadgets/rewrite/
main/java/org/apache/shindig/gadgets/servlet/
test/java/org/apache/shindig/gadgets/servlet/
Author: etnu
Date: Wed Jun 11 02:33:22 2008
New Revision: 666593
URL: http://svn.apache.org/viewvc?rev=666593&view=rev
Log:
Applied second round of proxy refactoring for SHINDIG-371.
This is an extensive change to ProxyHandler, moving all makeRequest functionality to MakeRequestHandler.
A common base class that handles the shared functionality is introduced.
Many new tests added for both handlers.
This introduces a security improvement for the open proxy: all proxied files will now be served as file attachments. This can be an effective deterrent for anyone using the open proxy as a phishing vector while still retaining the desired functionality.
Added:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyBase.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ProxyBaseTest.java
Modified:
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpGadgetContext.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestServlet.java
incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpServletResponseRecorder.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ProxyHandlerTest.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ProxyServletTest.java
incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ServletTestFixture.java
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java?rev=666593&r1=666592&r2=666593&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/rewrite/JavascriptTagMerger.java Wed Jun 11 02:33:22 2008
@@ -18,9 +18,9 @@
*/
package org.apache.shindig.gadgets.rewrite;
-import org.apache.shindig.gadgets.servlet.ProxyHandler;
-import org.apache.shindig.gadgets.spec.GadgetSpec;
import org.apache.shindig.common.util.Utf8UrlCoder;
+import org.apache.shindig.gadgets.servlet.ProxyBase;
+import org.apache.shindig.gadgets.spec.GadgetSpec;
import com.google.caja.lexer.HtmlTokenType;
import com.google.caja.lexer.Token;
@@ -60,7 +60,7 @@
// Force the mime-type to mimic browser expectation so rewriters
// can function properly
this.concatBase = concatBase
- + ProxyHandler.REWRITE_MIME_TYPE_PARAM
+ + ProxyBase.REWRITE_MIME_TYPE_PARAM
+ "=text/javascript&"
+ "gadget="
+ Utf8UrlCoder.encode(spec.getUrl().toString())
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpGadgetContext.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpGadgetContext.java?rev=666593&r1=666592&r2=666593&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpGadgetContext.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/HttpGadgetContext.java Wed Jun 11 02:33:22 2008
@@ -281,7 +281,8 @@
debug = getDebug(request);
view = getView(request);
userPrefs = getUserPrefs(request);
- tokenString = request.getParameter(ProxyHandler.SECURITY_TOKEN_PARAM);
+ // TODO: This shouldn't be depending on MakeRequest at all.
+ tokenString = request.getParameter(MakeRequestHandler.SECURITY_TOKEN_PARAM);
this.tokenDecoder = tokenDecoder;
}
}
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java?rev=666593&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestHandler.java Wed Jun 11 02:33:22 2008
@@ -0,0 +1,275 @@
+/*
+ * 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.servlet;
+
+import org.apache.shindig.common.SecurityToken;
+import org.apache.shindig.common.SecurityTokenDecoder;
+import org.apache.shindig.common.SecurityTokenException;
+import org.apache.shindig.gadgets.FeedProcessor;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.http.ContentFetcherFactory;
+import org.apache.shindig.gadgets.http.HttpFetcher;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.oauth.OAuthRequestParams;
+import org.apache.shindig.gadgets.rewrite.ContentRewriter;
+import org.apache.shindig.gadgets.spec.Auth;
+import org.apache.shindig.gadgets.spec.Preload;
+
+import com.google.common.collect.Maps;
+import com.google.inject.Inject;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.net.URI;
+import java.net.URLDecoder;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Handles gadgets.io.makeRequest requests.
+ *
+ * Unlike ProxyHandler, this may perform operations such as OAuth or signed fetch.
+ */
+public class MakeRequestHandler extends ProxyBase{
+ protected static final String UNPARSEABLE_CRUFT = "throw 1; < don't be evil' >";
+ protected static final String POST_DATA_PARAM = "postData";
+ protected static final String METHOD_PARAM = "httpMethod";
+ protected static final String SECURITY_TOKEN_PARAM = "st";
+ protected static final String HEADERS_PARAM = "headers";
+ protected static final String NOCACHE_PARAM = "nocache";
+ protected static final String SIGN_VIEWER = "signViewer";
+ protected static final String SIGN_OWNER = "signOwner";
+ protected static final String CONTENT_TYPE_PARAM = "contentType";
+ protected static final String NUM_ENTRIES_PARAM = "numEntries";
+ protected static final String DEFAULT_NUM_ENTRIES = "3";
+ protected static final String GET_SUMMARIES_PARAM = "getSummaries";
+
+ private final SecurityTokenDecoder securityTokenDecoder;
+ private final ContentFetcherFactory contentFetcherFactory;
+ private final ContentRewriter rewriter;
+
+ @Inject
+ public MakeRequestHandler(ContentFetcherFactory contentFetcherFactory,
+ SecurityTokenDecoder securityTokenDecoder,
+ ContentRewriter rewriter) {
+ this.contentFetcherFactory = contentFetcherFactory;
+ this.securityTokenDecoder = securityTokenDecoder;
+ this.rewriter = rewriter;
+ }
+
+ /**
+ * Executes a request, returning the response as JSON to be handled by makeRequest.
+ */
+ @Override
+ public void fetch(HttpServletRequest request, HttpServletResponse response)
+ throws GadgetException, IOException {
+ HttpRequest rcr = buildHttpRequest(request);
+
+ // Figure out whether authentication is required
+ Auth auth = Auth.parse(getParameter(request, Preload.AUTHZ_ATTR, ""));
+ SecurityToken authToken = null;
+ if (auth != Auth.NONE) {
+ authToken = extractAndValidateToken(request);
+ }
+
+ // Build the chain of fetchers that will handle the request
+ HttpFetcher fetcher = getHttpFetcher(auth, authToken, request);
+
+ // Serialize the response
+ HttpResponse results = fetcher.fetch(rcr);
+
+ // Serialize the response
+ String output = convertResponseToJson(authToken, request, results);
+
+ // Find and set the refresh interval
+ setResponseHeaders(request, response, results);
+
+ response.setStatus(HttpServletResponse.SC_OK);
+ response.setContentType("application/json; charset=utf-8");
+ response.getWriter().write(UNPARSEABLE_CRUFT + output);
+ }
+
+ /**
+ * Generate a remote content request based on the parameters
+ * sent from the client.
+ * @throws GadgetException
+ */
+ private HttpRequest buildHttpRequest(HttpServletRequest request) throws GadgetException {
+ try {
+ String encoding = request.getCharacterEncoding();
+ if (encoding == null) {
+ encoding = "UTF-8";
+ }
+
+ URI url = validateUrl(request.getParameter(URL_PARAM));
+ String method = request.getMethod();
+ Map<String, List<String>> headers = null;
+ byte[] postBody = null;
+
+ method = getParameter(request, METHOD_PARAM, "GET");
+ postBody = getParameter(request, POST_DATA_PARAM, "").getBytes();
+
+ String headerData = request.getParameter(HEADERS_PARAM);
+ if (headerData == null || headerData.length() == 0) {
+ headers = Collections.emptyMap();
+ } else {
+ // We actually only accept single key value mappings now.
+ headers = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
+ String[] headerList = headerData.split("&");
+ for (String header : headerList) {
+ String[] parts = header.split("=");
+ if (parts.length != 2) {
+ throw new GadgetException(GadgetException.Code.INTERNAL_SERVER_ERROR,
+ "Malformed header specified,");
+ }
+ headers.put(URLDecoder.decode(parts[0], encoding),
+ Arrays.asList(URLDecoder.decode(parts[1], encoding)));
+ }
+ }
+
+ removeUnsafeHeaders(headers);
+
+ HttpRequest.Options options = new HttpRequest.Options();
+ options.ignoreCache = "1".equals(request.getParameter(NOCACHE_PARAM));
+ if (request.getParameter(SIGN_VIEWER) != null) {
+ options.viewerSigned = Boolean.parseBoolean(request.getParameter(SIGN_VIEWER));
+ }
+ if (request.getParameter(SIGN_OWNER) != null) {
+ options.ownerSigned = Boolean.parseBoolean(request.getParameter(SIGN_OWNER));
+ }
+ if (request.getParameter(GADGET_PARAM) != null) {
+ options.gadgetUri = URI.create(request.getParameter(GADGET_PARAM));
+ }
+ options.rewriter = rewriter;
+
+ // Allow the rewriter to use an externally forced mime type. This is needed
+ // allows proper rewriting of <script src="x"/> where x is returned with
+ // a content type like text/html which unfortunately happens all too often
+ options.rewriteMimeType = request.getParameter(REWRITE_MIME_TYPE_PARAM);
+
+ return new HttpRequest(method, url, headers, postBody, options);
+ } catch (UnsupportedEncodingException e) {
+ throw new GadgetException(GadgetException.Code.INTERNAL_SERVER_ERROR, e);
+ }
+ }
+
+ /**
+ * Removes unsafe headers from the header set.
+ */
+ private void removeUnsafeHeaders(Map<String, List<String>> headers) {
+ // Host must be removed.
+ final String[] badHeaders = new String[] {
+ // No legitimate reason to over ride these.
+ // TODO: We probably need to test variations as well.
+ "Host", "Accept", "Accept-Encoding"
+ };
+ for (String bad : badHeaders) {
+ headers.remove(bad);
+ }
+ }
+
+ /**
+ * At the moment our fetcher chain is short. In the future we might add
+ * additional layers for things like caching or throttling.
+ *
+ * Whatever we do needs to return a reference to the OAuthFetcher, if it is
+ * present, so we can pull data out as needed.
+ */
+ private HttpFetcher getHttpFetcher(Auth auth, SecurityToken token, HttpServletRequest request)
+ throws GadgetException {
+ switch (auth) {
+ case NONE:
+ return contentFetcherFactory.get();
+ case SIGNED:
+ return contentFetcherFactory.getSigningFetcher(token);
+ case AUTHENTICATED:
+ return contentFetcherFactory.getOAuthFetcher(token, new OAuthRequestParams(request));
+ default:
+ return contentFetcherFactory.get();
+ }
+ }
+
+ /**
+ * Format a response as JSON, including additional JSON inserted by
+ * chained content fetchers.
+ */
+ private String convertResponseToJson(SecurityToken authToken, HttpServletRequest request,
+ HttpResponse results) throws GadgetException {
+ try {
+ JSONObject resp = new JSONObject();
+ String originalUrl = request.getParameter(ProxyBase.URL_PARAM);
+ String body = results.getResponseAsString();
+ if ("FEED".equals(request.getParameter(CONTENT_TYPE_PARAM))) {
+ resp.put("body", processFeed(originalUrl, request, body));
+ } else {
+ resp.put("body", body);
+ }
+ resp.put("rc", results.getHttpStatusCode());
+
+ if (authToken != null) {
+ String updatedAuthToken = authToken.getUpdatedToken();
+ if (updatedAuthToken != null) {
+ resp.put("st", updatedAuthToken);
+ }
+ }
+
+ // Merge in additional response data
+ for (Map.Entry<String, String> entry : results.getMetadata().entrySet()) {
+ resp.put(entry.getKey(), entry.getValue());
+ }
+ // Use raw param as key as URL may have to be decoded
+ return new JSONObject().put(originalUrl, resp).toString();
+ } catch (JSONException e) {
+ return "";
+ }
+ }
+
+ /**
+ * @param request
+ * @return A valid token for the given input.
+ */
+ private SecurityToken extractAndValidateToken(HttpServletRequest request)
+ throws GadgetException {
+ String token = getParameter(request, SECURITY_TOKEN_PARAM, "");
+ try {
+ return securityTokenDecoder.createToken(token);
+ } catch (SecurityTokenException e) {
+ throw new GadgetException(GadgetException.Code.INVALID_SECURITY_TOKEN, e);
+ }
+ }
+
+ /**
+ * Processes a feed (RSS or Atom) using FeedProcessor.
+ */
+ private String processFeed(String url, HttpServletRequest req, String xml)
+ throws GadgetException {
+ boolean getSummaries = Boolean.parseBoolean(getParameter(req, GET_SUMMARIES_PARAM, "false"));
+ int numEntries = Integer.parseInt(getParameter(req, NUM_ENTRIES_PARAM, DEFAULT_NUM_ENTRIES));
+ return new FeedProcessor().process(url, xml, getSummaries, numEntries).toString();
+ }
+}
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestServlet.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestServlet.java?rev=666593&r1=666592&r2=666593&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestServlet.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/MakeRequestServlet.java Wed Jun 11 02:33:22 2008
@@ -41,18 +41,18 @@
public class MakeRequestServlet extends InjectedServlet {
private final static Logger LOG = Logger.getLogger(MakeRequestServlet.class.getName());
- private ProxyHandler proxyHandler;
+ private MakeRequestHandler makeRequestHandler;
@Inject
- public void setProxyHandler(ProxyHandler proxyHandler) {
- this.proxyHandler = proxyHandler;
+ public void setMakeRequestHandler(MakeRequestHandler makeRequestHandler) {
+ this.makeRequestHandler = makeRequestHandler;
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException {
try {
- proxyHandler.fetchJson(request, response);
+ makeRequestHandler.fetch(request, response);
} catch (GadgetException e) {
// TODO: Move this logic into ProxyHandler / MakeRequestHandler.
outputError(e, response);
Added: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyBase.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyBase.java?rev=666593&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyBase.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyBase.java Wed Jun 11 02:33:22 2008
@@ -0,0 +1,111 @@
+/*
+ * 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.servlet;
+
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.http.HttpResponse;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Base class for proxy-based handlers.
+ */
+public abstract class ProxyBase {
+ protected static final String URL_PARAM = "url";
+ protected static final String REFRESH_PARAM = "refresh";
+ protected static final String GADGET_PARAM = "gadget";
+
+ // Public because of rewriter. Rewriter should be cleaned up.
+ public static final String REWRITE_MIME_TYPE_PARAM = "rewriteMime";
+
+ /**
+ * Validates the given url.
+ *
+ * @return A URI representing a validated form of the url.
+ * @throws GadgetException If the url is not valid.
+ */
+ protected URI validateUrl(String urlToValidate) throws GadgetException {
+ if (urlToValidate == null) {
+ throw new GadgetException(GadgetException.Code.INVALID_PARAMETER,
+ "url parameter is missing.");
+ }
+ try {
+ URI url = new URI(urlToValidate);
+ if (!"http".equals(url.getScheme()) && !"https".equals(url.getScheme())) {
+ throw new GadgetException(GadgetException.Code.INVALID_PARAMETER,
+ "Invalid request url scheme; only " +
+ "\"http\" and \"https\" supported.");
+ }
+ if (url.getPath() == null || url.getPath().length() == 0) {
+ // Forcibly set the path to "/" if it is empty
+ url = new URI(url.getScheme(),
+ url.getUserInfo(),
+ url.getHost(),
+ url.getPort(),
+ "/", url.getQuery(),
+ url.getFragment());
+ }
+ return url;
+ } catch (URISyntaxException use) {
+ throw new GadgetException(GadgetException.Code.INVALID_PARAMETER,
+ "url parameter is not a valid url.");
+ }
+ }
+
+ /**
+ * Extracts the first parameter from the parameter map with the given name.
+ *
+ * @param request The request to extract parameters from.
+ * @param name The name of the parameter to retrieve.
+ * @param defaultValue The default value to use if the parameter is not set.
+ * @return The parameter, if found, or defaultValue
+ */
+ protected String getParameter(HttpServletRequest request, String name, String defaultValue) {
+ String ret = request.getParameter(name);
+ return ret == null ? defaultValue : ret;
+ }
+
+ /**
+ * Sets cache control headers for the response.
+ */
+ protected void setResponseHeaders(HttpServletRequest request,
+ HttpServletResponse response, HttpResponse results) {
+ int refreshInterval = 0;
+ if (results.isStrictNoCache()) {
+ refreshInterval = 0;
+ } else if (request.getParameter(REFRESH_PARAM) != null) {
+ refreshInterval = Integer.valueOf(request.getParameter(REFRESH_PARAM));
+ } else {
+ refreshInterval = Math.max(60 * 60, (int)(results.getExpiration() / 1000));
+ }
+ HttpUtil.setCachingHeaders(response, refreshInterval);
+ response.setHeader("Content-Disposition", "attachment;filename=p.txt");
+ }
+
+ /**
+ * Processes the given request.
+ */
+ abstract public void fetch(HttpServletRequest request, HttpServletResponse response)
+ throws GadgetException, IOException;
+}
Modified: incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java?rev=666593&r1=666592&r2=666593&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/main/java/org/apache/shindig/gadgets/servlet/ProxyHandler.java Wed Jun 11 02:33:22 2008
@@ -18,68 +18,30 @@
*/
package org.apache.shindig.gadgets.servlet;
-import org.apache.shindig.common.SecurityToken;
-import org.apache.shindig.common.SecurityTokenDecoder;
-import org.apache.shindig.common.SecurityTokenException;
-import org.apache.shindig.gadgets.FeedProcessor;
import org.apache.shindig.gadgets.GadgetException;
import org.apache.shindig.gadgets.LockedDomainService;
-import org.apache.shindig.gadgets.http.ContentFetcherFactory;
import org.apache.shindig.gadgets.http.HttpFetcher;
import org.apache.shindig.gadgets.http.HttpRequest;
import org.apache.shindig.gadgets.http.HttpResponse;
-import org.apache.shindig.gadgets.oauth.OAuthRequestParams;
import org.apache.shindig.gadgets.rewrite.ContentRewriter;
-import org.apache.shindig.gadgets.spec.Auth;
-import org.apache.shindig.gadgets.spec.Preload;
import com.google.inject.Inject;
-import org.json.JSONException;
-import org.json.JSONObject;
-
import java.io.IOException;
-import java.io.UnsupportedEncodingException;
import java.net.URI;
-import java.net.URISyntaxException;
-import java.net.URLDecoder;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
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;
-public class ProxyHandler {
- public static final String UNPARSEABLE_CRUFT = "throw 1; < don't be evil' >";
- public static final String POST_DATA_PARAM = "postData";
- public static final String METHOD_PARAM = "httpMethod";
- public static final String SECURITY_TOKEN_PARAM = "st";
- public static final String HEADERS_PARAM = "headers";
- public static final String NOCACHE_PARAM = "nocache";
- public static final String REWRITE_MIME_TYPE_PARAM = "rewriteMime";
- public static final String SIGN_VIEWER = "signViewer";
- public static final String SIGN_OWNER = "signOwner";
- public static final String URL_PARAM = "url";
- public static final String REFRESH_PARAM = "refresh";
- public static final String GADGET_PARAM = "gadget";
- public static final String CONTENT_TYPE_PARAM = "contentType";
- public static final String NUM_ENTRIES_PARAM = "numEntries";
- public static final String DEFAULT_NUM_ENTRIES = "3";
- public static final String GET_SUMMARIES_PARAM = "getSummaries";
-
+public class ProxyHandler extends ProxyBase {
private static final Logger logger =
Logger.getLogger(ProxyHandler.class.getPackage().getName());
-
- private final SecurityTokenDecoder securityTokenDecoder;
-
private final static Set<String> DISALLOWED_RESPONSE_HEADERS
= new HashSet<String>();
static {
@@ -98,257 +60,60 @@
// 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 HttpFetcher fetcher;
private final LockedDomainService domainLocker;
private final ContentRewriter rewriter;
@Inject
- public void setContentFetcher(ContentFetcherFactory contentFetcherFactory) {
- this.contentFetcherFactory = contentFetcherFactory;
- }
-
- @Inject
- public ProxyHandler(ContentFetcherFactory contentFetcherFactory,
- SecurityTokenDecoder securityTokenDecoder,
+ public ProxyHandler(HttpFetcher fetcher,
LockedDomainService lockedDomainService,
ContentRewriter rewriter) {
- this.contentFetcherFactory = contentFetcherFactory;
- this.securityTokenDecoder = securityTokenDecoder;
+ this.fetcher = fetcher;
this.domainLocker = lockedDomainService;
this.rewriter = rewriter;
}
/**
- * Sets cache control headers for the response.
+ * Generate a remote content request based on the parameters sent from the client.
*/
- private void setCachingHeaders(HttpServletRequest request,
- HttpServletResponse response, HttpResponse results) {
- int refreshInterval = 0;
- if (results.isStrictNoCache()) {
- refreshInterval = 0;
- } else if (request.getParameter(REFRESH_PARAM) != null) {
- refreshInterval = Integer.valueOf(request.getParameter(REFRESH_PARAM));
- } else {
- refreshInterval = Math.max(60 * 60, (int)(results.getExpiration() / 1000));
+ private HttpRequest buildHttpRequest(HttpServletRequest request) throws GadgetException {
+ URI url = validateUrl(request.getParameter(URL_PARAM));
+ HttpRequest.Options options = new HttpRequest.Options();
+ if (request.getParameter(GADGET_PARAM) != null) {
+ options.gadgetUri = URI.create(request.getParameter(GADGET_PARAM));
}
- HttpUtil.setCachingHeaders(response, refreshInterval);
- }
-
- /**
- * Retrieves a file and returns it as a json response.
- *
- * @param request
- * @param response
- * @throws IOException
- * @throws GadgetException
- */
- public void fetchJson(HttpServletRequest request,
- HttpServletResponse response)
- throws IOException, GadgetException {
-
- // Build up the request to make
- HttpRequest rcr = buildHttpRequest(request);
-
- // Figure out whether authentication is required
- Auth auth = Auth.parse(getParameter(request, Preload.AUTHZ_ATTR, ""));
- SecurityToken authToken = null;
- if (auth != Auth.NONE) {
- authToken = extractAndValidateToken(request);
- }
-
- // Build the chain of fetchers that will handle the request
- HttpFetcher fetcher = getHttpFetcher(auth, authToken, request, response);
-
- // Do the fetch
- HttpResponse results = fetcher.fetch(rcr);
-
- // Serialize the response
- String output = serializeJsonResponse(authToken, request, results);
-
- // Find and set the refresh interval
- setCachingHeaders(request, response, results);
-
- response.setStatus(HttpServletResponse.SC_OK);
- response.setContentType("application/json; charset=utf-8");
- response.setHeader("Content-Disposition", "attachment;filename=p.txt");
- response.getWriter().write(output);
- }
-
- /**
- * Generate a remote content request based on the parameters
- * sent from the client.
- * @throws GadgetException
- */
- @SuppressWarnings("unchecked")
- private HttpRequest buildHttpRequest(
- HttpServletRequest request) throws GadgetException {
- try {
- String encoding = request.getCharacterEncoding();
- if (encoding == null) {
- encoding = "UTF-8";
- }
-
- URI url = validateUrl(request.getParameter(URL_PARAM));
- String method = request.getMethod();
- Map<String, List<String>> headers = null;
- byte[] postBody = null;
-
- if ("POST".equals(method)) {
- method = getParameter(request, METHOD_PARAM, "GET");
- postBody = getParameter(request, POST_DATA_PARAM, "").getBytes();
-
- String headerData = request.getParameter(HEADERS_PARAM);
- if (headerData == null || headerData.length() == 0) {
- headers = Collections.emptyMap();
- } else {
- // We actually only accept single key value mappings now.
- headers = new TreeMap<String, List<String>>(
- String.CASE_INSENSITIVE_ORDER);
- String[] headerList = headerData.split("&");
- for (String header : headerList) {
- String[] parts = header.split("=");
- if (parts.length != 2) {
- throw new GadgetException(
- GadgetException.Code.INTERNAL_SERVER_ERROR,
- "malformed header specified");
- }
- headers.put(URLDecoder.decode(parts[0], encoding),
- Arrays.asList(URLDecoder.decode(parts[1], encoding)));
- }
- }
- } else {
- postBody = null;
- headers = new TreeMap<String, List<String>>(
- String.CASE_INSENSITIVE_ORDER);
- Enumeration<String> headerNames = request.getHeaderNames();
- while (headerNames.hasMoreElements()) {
- String header = headerNames.nextElement();
- headers.put(header, Collections.list(request.getHeaders(header)));
- }
- }
-
- removeUnsafeHeaders(headers);
-
- HttpRequest.Options options =
- new HttpRequest.Options();
- options.ignoreCache = "1".equals(request.getParameter(NOCACHE_PARAM));
- if (request.getParameter(SIGN_VIEWER) != null) {
- options.viewerSigned = Boolean
- .parseBoolean(request.getParameter(SIGN_VIEWER));
- }
- if (request.getParameter(SIGN_OWNER) != null) {
- options.ownerSigned = Boolean
- .parseBoolean(request.getParameter(SIGN_OWNER));
- }
- if (request.getParameter(GADGET_PARAM) != null) {
- options.gadgetUri = URI.create(request.getParameter(GADGET_PARAM));
- }
- options.rewriter = rewriter;
-
- // Allow the rewriter to use an externally forced mime type. This is needed
- // allows proper rewriting of <script src="x"/> where x is returned with
- // a content type like text/html which unfortunately happens all too often
- options.rewriteMimeType = request.getParameter(REWRITE_MIME_TYPE_PARAM);
-
- return new HttpRequest(
- method, url, headers, postBody, options);
- } catch (UnsupportedEncodingException e) {
- throw new GadgetException(GadgetException.Code.INTERNAL_SERVER_ERROR, e);
- }
- }
-
- /**
- * At the moment our fetcher chain is short. In the future we might add
- * additional layers for things like caching or throttling.
- *
- * Whatever we do needs to return a reference to the OAuthFetcher, if it is
- * present, so we can pull data out as needed.
- */
- private HttpFetcher getHttpFetcher(Auth auth, SecurityToken authToken,
- HttpServletRequest request, HttpServletResponse response)
- throws GadgetException {
- switch (auth) {
- case NONE:
- return contentFetcherFactory.get();
- case SIGNED:
- return contentFetcherFactory.getSigningFetcher(authToken);
- case AUTHENTICATED:
- return contentFetcherFactory.getOAuthFetcher(
- authToken, new OAuthRequestParams(request));
- default:
- return contentFetcherFactory.get();
- }
- }
-
- /**
- * Format a response as JSON, including additional JSON inserted by
- * chained content fetchers.
- */
- private String serializeJsonResponse(SecurityToken authToken,
- HttpServletRequest request, HttpResponse results) throws GadgetException {
- try {
- JSONObject resp = new JSONObject();
- String originalUrl = request.getParameter(URL_PARAM);
- String body = results.getResponseAsString();
- if ("FEED".equals(request.getParameter(CONTENT_TYPE_PARAM))) {
- resp.put("body", processFeed(originalUrl, request, body));
- } else {
- resp.put("body", body);
- }
- resp.put("rc", results.getHttpStatusCode());
+ options.rewriter = rewriter;
- if (authToken != null) {
- String updatedAuthToken = authToken.getUpdatedToken();
- if (updatedAuthToken != null) {
- resp.put("st", updatedAuthToken);
- }
- }
+ // Allow the rewriter to use an externally forced mime type. This is needed
+ // allows proper rewriting of <script src="x"/> where x is returned with
+ // a content type like text/html which unfortunately happens all too often
+ options.rewriteMimeType = request.getParameter(REWRITE_MIME_TYPE_PARAM);
- // Merge in additional response data
- for (Map.Entry<String, String> entry : results.getMetadata().entrySet()) {
- resp.put(entry.getKey(), entry.getValue());
- }
- // Use raw param as key as URL may have to be decoded
- JSONObject json = new JSONObject().put(originalUrl, resp);
- return UNPARSEABLE_CRUFT + json.toString();
- } catch (JSONException e) {
- return "";
- }
+ return new HttpRequest(url, options);
}
- /**
- * This is called for embedding images inline in gadgets, e.g. via img src
- * links created with IG_Embed
- *
- * @param request
- * @param response
- * @throws IOException
- * @throws GadgetException
- */
- public void fetch(HttpServletRequest request,
- HttpServletResponse response)
+ @Override
+ public void fetch(HttpServletRequest request, HttpServletResponse response)
throws IOException, GadgetException {
+ if (request.getHeader("If-Modified-Since") != null) {
+ response.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
+ return;
+ }
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, "") +
+ 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;
- }
-
HttpRequest rcr = buildHttpRequest(request);
- HttpResponse results = contentFetcherFactory.get().fetch(rcr);
+ HttpResponse results = fetcher.fetch(rcr);
- setCachingHeaders(request, response, results);
+ setResponseHeaders(request, response, results);
for (Map.Entry<String, List<String>> entry : results.getAllHeaders().entrySet()) {
String name = entry.getKey();
@@ -369,93 +134,4 @@
response.getOutputStream().write(results.getResponseAsBytes());
}
-
- /**
- * Removes unsafe headers from the header set.
- * @param headers
- */
- private void removeUnsafeHeaders(Map<String, List<String>> headers) {
- // Host must be removed.
- final String[] badHeaders = new String[] {
- // No legitimate reason to over ride these.
- // TODO: We probably need to test variations as well.
- "Host", "Accept", "Accept-Encoding"
- };
- for (String bad : badHeaders) {
- headers.remove(bad);
- }
- }
-
- /**
- * Validates that the given url is valid for this request.
- *
- * @param urlToValidate
- * @return A URL object of the URL
- * @throws GadgetException if the URL fails security checks or is malformed.
- */
- public URI validateUrl(String urlToValidate) throws GadgetException {
- if (urlToValidate == null) {
- throw new GadgetException(GadgetException.Code.INVALID_PARAMETER,
- "url parameter is missing.");
- }
- try {
- URI url = new URI(urlToValidate);
- if (!"http".equals(url.getScheme()) && !"https".equals(url.getScheme())) {
- throw new GadgetException(GadgetException.Code.INVALID_PARAMETER,
- "Invalid request url scheme; only " +
- "\"http\" and \"https\" supported.");
- }
- if (url.getPath() == null || url.getPath().length() == 0) {
- // Forcibly set the path to "/" if it is empty
- url = new URI(url.getScheme(),
- url.getUserInfo(),
- url.getHost(),
- url.getPort(),
- "/", url.getQuery(),
- url.getFragment());
- }
- return url;
- } catch (URISyntaxException use) {
- throw new GadgetException(GadgetException.Code.INVALID_PARAMETER,
- "url parameter is not a valid url.");
- }
- }
-
- /**
- * @param request
- * @return A valid token for the given input.
- */
- private SecurityToken extractAndValidateToken(HttpServletRequest request)
- throws GadgetException {
- String token = getParameter(request, SECURITY_TOKEN_PARAM, "");
- try {
- return securityTokenDecoder.createToken(token);
- } catch (SecurityTokenException e) {
- throw new GadgetException(GadgetException.Code.INVALID_SECURITY_TOKEN, e);
- }
- }
-
- /**
- * Processes a feed (RSS or Atom) using FeedProcessor.
- */
- private String processFeed(String url, HttpServletRequest req, String xml)
- throws GadgetException {
- boolean getSummaries = Boolean.parseBoolean(
- getParameter(req, GET_SUMMARIES_PARAM, "false"));
- int numEntries = Integer.parseInt(
- getParameter(req, NUM_ENTRIES_PARAM, DEFAULT_NUM_ENTRIES));
- return new FeedProcessor().process(url, xml, getSummaries, numEntries).toString();
- }
-
- /**
- * Extracts the first parameter from the parameter map with the given name.
- * @param request
- * @param name
- * @return The parameter, if found, or defaultValue
- */
- private static String getParameter(HttpServletRequest request,
- String name, String defaultValue) {
- String ret = request.getParameter(name);
- return ret == null ? defaultValue : ret;
- }
}
Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpServletResponseRecorder.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpServletResponseRecorder.java?rev=666593&r1=666592&r2=666593&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpServletResponseRecorder.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpServletResponseRecorder.java Wed Jun 11 02:33:22 2008
@@ -18,9 +18,14 @@
*/
package org.apache.shindig.gadgets.servlet;
+import org.apache.shindig.common.util.DateUtil;
+
+import com.google.common.collect.Maps;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
+import java.util.Map;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
@@ -32,6 +37,7 @@
public class HttpServletResponseRecorder extends HttpServletResponseWrapper {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
private final PrintWriter writer = new PrintWriter(baos);
+ private final Map<String, String> headers = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
private int httpStatusCode = 200;
public HttpServletResponseRecorder(HttpServletResponse response) {
@@ -55,6 +61,10 @@
return httpStatusCode;
}
+ public String getHeader(String name) {
+ return headers.get(name);
+ }
+
@Override
public PrintWriter getWriter() {
return writer;
@@ -71,6 +81,26 @@
}
@Override
+ public void addHeader(String name, String value) {
+ headers.put(name, value);
+ }
+
+ @Override
+ public void setHeader(String name, String value) {
+ addHeader(name, value);
+ }
+
+ @Override
+ public void addDateHeader(String name, long date) {
+ headers.put(name, DateUtil.formatDate(date));
+ }
+
+ @Override
+ public void setDateHeader(String name, long date) {
+ addDateHeader(name, date);
+ }
+
+ @Override
public void setStatus(int httpStatusCode) {
this.httpStatusCode = httpStatusCode;
}
Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java?rev=666593&r1=666592&r2=666593&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/HttpTestFixture.java Wed Jun 11 02:33:22 2008
@@ -25,7 +25,6 @@
import org.apache.shindig.gadgets.rewrite.NoOpContentRewriter;
public abstract class HttpTestFixture extends GadgetTestFixture {
- public final ProxyHandler proxyHandler;
public final GadgetRenderingTask gadgetRenderer;
public final JsonRpcHandler jsonRpcHandler;
public final ContentFetcherFactory contentFetcherFactory
@@ -37,11 +36,6 @@
public HttpTestFixture() {
super();
- proxyHandler = new ProxyHandler(
- contentFetcherFactory,
- securityTokenDecoder,
- lockedDomainService,
- rewriter);
gadgetRenderer = new GadgetRenderingTask(gadgetServer, bundleFactory,
registry, containerConfig, urlGenerator, securityTokenDecoder, lockedDomainService);
jsonRpcHandler = new JsonRpcHandler(executor, gadgetServer, urlGenerator);
Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java?rev=666593&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestHandlerTest.java Wed Jun 11 02:33:22 2008
@@ -0,0 +1,381 @@
+/*
+ * 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.servlet;
+
+import static junitx.framework.StringAssert.assertStartsWith;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.isA;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import org.apache.shindig.common.SecurityToken;
+import org.apache.shindig.common.SecurityTokenException;
+import org.apache.shindig.gadgets.FakeGadgetToken;
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.http.HttpFetcher;
+import org.apache.shindig.gadgets.http.HttpRequest;
+import org.apache.shindig.gadgets.http.HttpResponse;
+import org.apache.shindig.gadgets.spec.Auth;
+import org.apache.shindig.gadgets.spec.Preload;
+
+import com.google.common.collect.Lists;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.List;
+
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Tests for MakeRequestHandler.
+ */
+public class MakeRequestHandlerTest {
+ private static final String REQUEST_URL = "http://example.org/file";
+ private static final String REQUEST_BODY = "I+am+the+request+body!foo=baz%20la";
+ private static final String RESPONSE_BODY = "makeRequest response body";
+ private static final SecurityToken DUMMY_TOKEN = new FakeGadgetToken();
+
+ private final ServletTestFixture fixture = new ServletTestFixture();
+ private final MakeRequestHandler handler = new MakeRequestHandler(fixture.contentFetcherFactory,
+ fixture.securityTokenDecoder, fixture.rewriter);
+ private final HttpServletResponseRecorder recorder
+ = new HttpServletResponseRecorder(fixture.response);
+
+ private void expectGetAndReturnBody(String response) throws Exception {
+ expectGetAndReturnBody(fixture.httpFetcher, response);
+ }
+
+ private void expectGetAndReturnBody(HttpFetcher fetcher, String response) throws Exception {
+ HttpRequest request = HttpRequest.getRequest(URI.create(REQUEST_URL), false);
+ expect(fetcher.fetch(request)).andReturn(new HttpResponse(response));
+ }
+
+ private void expectPostAndReturnBody(String postData, String response) throws Exception {
+ expectPostAndReturnBody(fixture.httpFetcher, postData, response);
+ }
+
+ private void expectPostAndReturnBody(HttpFetcher fetcher, String postData, String response)
+ throws Exception {
+ HttpRequest request = new HttpRequest(URI.create(REQUEST_URL), REQUEST_BODY.getBytes("UTF-8"));
+ expect(fixture.request.getParameter(MakeRequestHandler.METHOD_PARAM)).andReturn("POST");
+ expect(fixture.request.getParameter(MakeRequestHandler.POST_DATA_PARAM))
+ .andReturn(REQUEST_BODY);
+ expect(fetcher.fetch(request)).andReturn(new HttpResponse(response));
+ }
+
+ private JSONObject extractJsonFromResponse() throws JSONException {
+ String body = recorder.getResponseAsString();
+ assertStartsWith(MakeRequestHandler.UNPARSEABLE_CRUFT, body);
+ body = body.substring(MakeRequestHandler.UNPARSEABLE_CRUFT.length());
+ return new JSONObject(body).getJSONObject(REQUEST_URL);
+ }
+
+ @Before
+ public void setUp() {
+ expect(fixture.request.getMethod()).andReturn("POST").anyTimes();
+ expect(fixture.request.getParameter(MakeRequestHandler.URL_PARAM))
+ .andReturn(REQUEST_URL).anyTimes();
+ }
+
+ @Test
+ public void getRequest() throws Exception {
+ expectGetAndReturnBody(RESPONSE_BODY);
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+
+ JSONObject results = extractJsonFromResponse();
+ assertEquals(HttpResponse.SC_OK, results.getInt("rc"));
+ assertEquals(RESPONSE_BODY, results.get("body"));
+ }
+
+ @Test
+ public void explicitHeaders() throws Exception {
+ String headerString = "X-Foo=bar&X-Bar=baz%20foo";
+
+ final List<HttpRequest> requests = Lists.newArrayList();
+ HttpFetcher fakeFetcher = new HttpFetcher() {
+ public HttpResponse fetch(HttpRequest request) {
+ requests.add(request);
+ return new HttpResponse(RESPONSE_BODY);
+ }
+ };
+
+ fixture.reset();
+ setUp();
+ expect(fixture.contentFetcherFactory.get()).andReturn(fakeFetcher);
+ expect(fixture.request.getParameter(MakeRequestHandler.HEADERS_PARAM)).andReturn(headerString);
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ fixture.verify();
+
+ assertEquals("bar", requests.get(0).getHeader("X-Foo"));
+ assertEquals("baz foo", requests.get(0).getHeader("X-Bar"));
+
+ JSONObject results = extractJsonFromResponse();
+ assertEquals(HttpResponse.SC_OK, results.getInt("rc"));
+ assertEquals(RESPONSE_BODY, results.get("body"));
+ }
+
+ @Test
+ public void postRequest() throws Exception {
+ expectPostAndReturnBody(REQUEST_BODY, RESPONSE_BODY);
+ expect(fixture.request.getParameter(MakeRequestHandler.METHOD_PARAM)).andReturn("POST");
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ assertEquals(HttpResponse.SC_OK, results.getInt("rc"));
+ assertEquals(RESPONSE_BODY, results.get("body"));
+ }
+
+ @Test
+ public void fetchContentTypeFeed() throws Exception {
+ String entryTitle = "Feed title";
+ String entryLink = "http://example.org/entry/0/1";
+ String entrySummary = "This is the summary";
+ String rss = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
+ "<rss version=\"2.0\"><channel>" +
+ "<title>dummy</title>" +
+ "<link>http://example.org/</link>" +
+ "<item>" +
+ "<title>" + entryTitle + "</title>" +
+ "<link>" + entryLink + "</link>" +
+ "<description>" + entrySummary + "</description>" +
+ "</item>" +
+ "</channel></rss>";
+
+ expectGetAndReturnBody(rss);
+ expect(fixture.request.getParameter(MakeRequestHandler.CONTENT_TYPE_PARAM)).andReturn("FEED");
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ JSONObject feed = new JSONObject(results.getString("body"));
+ JSONObject entry = feed.getJSONArray("Entry").getJSONObject(0);
+
+ assertEquals(entryTitle, entry.getString("Title"));
+ assertEquals(entryLink, entry.getString("Link"));
+ assertNull("getSummaries has the wrong default value (should be false).",
+ entry.optString("Summary", null));
+ }
+
+ @Test
+ public void fetchFeedWithParameters() throws Exception {
+ String entryTitle = "Feed title";
+ String entryLink = "http://example.org/entry/0/1";
+ String entrySummary = "This is the summary";
+ String rss = "<?xml version=\"1.0\" encoding=\"utf-8\"?>" +
+ "<rss version=\"2.0\"><channel>" +
+ "<title>dummy</title>" +
+ "<link>http://example.org/</link>" +
+ "<item>" +
+ "<title>" + entryTitle + "</title>" +
+ "<link>" + entryLink + "</link>" +
+ "<description>" + entrySummary + "</description>" +
+ "</item>" +
+ "<item>" +
+ "<title>" + entryTitle + "</title>" +
+ "<link>" + entryLink + "</link>" +
+ "<description>" + entrySummary + "</description>" +
+ "</item>" +
+ "<item>" +
+ "<title>" + entryTitle + "</title>" +
+ "<link>" + entryLink + "</link>" +
+ "<description>" + entrySummary + "</description>" +
+ "</item>" +
+ "</channel></rss>";
+
+ expectGetAndReturnBody(rss);
+ expect(fixture.request.getParameter(MakeRequestHandler.GET_SUMMARIES_PARAM)).andReturn("true");
+ expect(fixture.request.getParameter(MakeRequestHandler.NUM_ENTRIES_PARAM)).andReturn("2");
+ expect(fixture.request.getParameter(MakeRequestHandler.CONTENT_TYPE_PARAM)).andReturn("FEED");
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ JSONObject feed = new JSONObject(results.getString("body"));
+ JSONArray feeds = feed.getJSONArray("Entry");
+
+ assertEquals("numEntries not parsed correctly.", 2, feeds.length());
+
+ JSONObject entry = feeds.getJSONObject(1);
+ assertEquals(entryTitle, entry.getString("Title"));
+ assertEquals(entryLink, entry.getString("Link"));
+ assertTrue("getSummaries not parsed correctly.", entry.has("Summary"));
+ assertEquals(entrySummary, entry.getString("Summary"));
+ }
+
+ @Test
+ public void fetchEmptyDocument() throws Exception {
+ expectGetAndReturnBody("");
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ assertEquals(HttpResponse.SC_OK, results.getInt("rc"));
+ assertEquals("", results.get("body"));
+ }
+
+ @Test
+ public void signedGetRequest() throws Exception {
+ // Doesn't actually sign since it returns the standard fetcher.
+ // Signing tests are in SigningFetcherTest
+ expect(fixture.securityTokenDecoder.createToken("fake-token")).andReturn(DUMMY_TOKEN);
+ expect(fixture.request.getParameter(MakeRequestHandler.SECURITY_TOKEN_PARAM))
+ .andReturn("fake-token").atLeastOnce();
+ expect(fixture.request.getParameter(Preload.AUTHZ_ATTR))
+ .andReturn(Auth.SIGNED.toString()).atLeastOnce();
+ expect(fixture.signingFetcher.fetch(isA(HttpRequest.class)))
+ .andReturn(new HttpResponse(RESPONSE_BODY));
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ assertEquals(RESPONSE_BODY, results.get("body"));
+ }
+
+ @Test
+ public void signedPostRequest() throws Exception {
+ // Doesn't actually sign since it returns the standard fetcher.
+ // Signing tests are in SigningFetcherTest
+ expectPostAndReturnBody(fixture.signingFetcher, REQUEST_BODY, RESPONSE_BODY);
+ expect(fixture.securityTokenDecoder.createToken("fake-token")).andReturn(DUMMY_TOKEN);
+ expect(fixture.request.getParameter(MakeRequestHandler.SECURITY_TOKEN_PARAM))
+ .andReturn("fake-token").atLeastOnce();
+ expect(fixture.request.getParameter(Preload.AUTHZ_ATTR))
+ .andReturn(Auth.SIGNED.toString()).atLeastOnce();
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ assertEquals(RESPONSE_BODY, results.get("body"));
+ assertFalse("A security token was returned when it was not requested.",
+ results.has("st"));
+ }
+
+ @Test
+ public void changeSecurityToken() throws Exception {
+ // Doesn't actually sign since it returns the standard fetcher.
+ // Signing tests are in SigningFetcherTest
+ expectGetAndReturnBody(fixture.signingFetcher, RESPONSE_BODY);
+ FakeGadgetToken authToken = new FakeGadgetToken("updated");
+ expect(fixture.securityTokenDecoder.createToken("fake-token")).andReturn(authToken);
+ expect(fixture.request.getParameter(MakeRequestHandler.SECURITY_TOKEN_PARAM))
+ .andReturn("fake-token").atLeastOnce();
+ expect(fixture.request.getParameter(Preload.AUTHZ_ATTR))
+ .andReturn(Auth.SIGNED.toString()).atLeastOnce();
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ assertEquals(RESPONSE_BODY, results.get("body"));
+ assertEquals("updated", results.getString("st"));
+ }
+
+ @Test
+ public void doOAuthRequest() throws Exception {
+ // Doesn't actually do oauth dance since it returns the standard fetcher.
+ // OAuth tests are in OAuthFetcherTest
+ expectGetAndReturnBody(fixture.oauthFetcher, RESPONSE_BODY);
+ FakeGadgetToken authToken = new FakeGadgetToken("updated");
+ expect(fixture.securityTokenDecoder.createToken("fake-token")).andReturn(authToken);
+ expect(fixture.request.getParameter(MakeRequestHandler.SECURITY_TOKEN_PARAM))
+ .andReturn("fake-token").atLeastOnce();
+ expect(fixture.request.getParameter(Preload.AUTHZ_ATTR))
+ .andReturn(Auth.AUTHENTICATED.toString()).atLeastOnce();
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ assertEquals(HttpResponse.SC_OK, results.getInt("rc"));
+ assertEquals(RESPONSE_BODY, results.get("body"));
+ }
+
+ @Test
+ public void invalidSigningTypeTreatedAsNone() throws Exception {
+ expectGetAndReturnBody(RESPONSE_BODY);
+ expect(fixture.request.getParameter(Preload.AUTHZ_ATTR)).andReturn("garbage");
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ assertEquals(HttpResponse.SC_OK, results.getInt("rc"));
+ assertEquals(RESPONSE_BODY, results.get("body"));
+ }
+
+ @Test
+ public void badHttpResponseIsPropagated() throws Exception {
+ HttpRequest request = HttpRequest.getRequest(URI.create(REQUEST_URL), false);
+ expect(fixture.httpFetcher.fetch(request)).andReturn(HttpResponse.error());
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ assertEquals(HttpResponse.SC_INTERNAL_SERVER_ERROR, results.getInt("rc"));
+ }
+
+ @Test(expected = GadgetException.class)
+ public void badSecurityTokenThrows() throws Exception {
+ expect(fixture.request.getParameter(MakeRequestHandler.SECURITY_TOKEN_PARAM))
+ .andReturn("fake-token").atLeastOnce();
+ expect(fixture.request.getParameter(Preload.AUTHZ_ATTR))
+ .andReturn(Auth.SIGNED.toString()).atLeastOnce();
+ expect(fixture.securityTokenDecoder.createToken("fake-token"))
+ .andThrow(new SecurityTokenException("No!"));
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+
+ assertTrue("Response for bad tokens should not be SC_OK",
+ HttpServletResponse.SC_OK != recorder.getHttpStatusCode());
+ }
+
+ @Test
+ public void metadataCopied() throws Exception {
+ HttpRequest request = HttpRequest.getRequest(URI.create(REQUEST_URL), false);
+ HttpResponse response = new HttpResponse("foo");
+ response.getMetadata().put("foo", RESPONSE_BODY);
+ expect(fixture.httpFetcher.fetch(request)).andReturn(response);
+ fixture.replay();
+
+ handler.fetch(fixture.request, recorder);
+ JSONObject results = extractJsonFromResponse();
+
+ assertEquals(RESPONSE_BODY, results.getString("foo"));
+ }
+}
Modified: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java?rev=666593&r1=666592&r2=666593&view=diff
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java (original)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/MakeRequestServletTest.java Wed Jun 11 02:33:22 2008
@@ -42,7 +42,7 @@
/**
* Tests for MakeRequestServlet.
*
- * Tests are trivial; real tests are in ProxyHandlerTest.
+ * Tests are trivial; real tests are in MakeRequestHandlerTest.
*/
public class MakeRequestServletTest {
private static final String REQUEST_URL = "http://example.org/file";
@@ -53,6 +53,8 @@
private final ServletTestFixture fixture = new ServletTestFixture();
private final MakeRequestServlet servlet = new MakeRequestServlet();
+ private final MakeRequestHandler handler = new MakeRequestHandler(fixture.contentFetcherFactory,
+ fixture.securityTokenDecoder, fixture.rewriter);
private final HttpServletResponseRecorder recorder
= new HttpServletResponseRecorder(fixture.response);
private final HttpRequest request = HttpRequest.getRequest(URI.create(REQUEST_URL), false);
@@ -60,11 +62,11 @@
@Before
public void setUp() {
- servlet.setProxyHandler(fixture.proxyHandler);
+ servlet.setMakeRequestHandler(handler);
expect(fixture.request.getHeaderNames()).andReturn(EMPTY_ENUM).anyTimes();
- expect(fixture.request.getParameter(ProxyHandler.METHOD_PARAM))
+ expect(fixture.request.getParameter(MakeRequestHandler.METHOD_PARAM))
.andReturn("GET").anyTimes();
- expect(fixture.request.getParameter(ProxyHandler.URL_PARAM)).andReturn(REQUEST_URL).anyTimes();
+ expect(fixture.request.getParameter(ProxyBase.URL_PARAM)).andReturn(REQUEST_URL).anyTimes();
}
private void setupGet() {
@@ -78,8 +80,8 @@
private void assertResponseOk(int expectedStatus, String expectedBody) throws JSONException {
if (recorder.getHttpStatusCode() == HttpServletResponse.SC_OK) {
String body = recorder.getResponseAsString();
- assertStartsWith(ProxyHandler.UNPARSEABLE_CRUFT, body);
- body = body.substring(ProxyHandler.UNPARSEABLE_CRUFT.length());
+ assertStartsWith(MakeRequestHandler.UNPARSEABLE_CRUFT, body);
+ body = body.substring(MakeRequestHandler.UNPARSEABLE_CRUFT.length());
JSONObject object = new JSONObject(body);
object = object.getJSONObject(REQUEST_URL);
assertEquals(expectedStatus, object.getInt("rc"));
Added: incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ProxyBaseTest.java
URL: http://svn.apache.org/viewvc/incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ProxyBaseTest.java?rev=666593&view=auto
==============================================================================
--- incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ProxyBaseTest.java (added)
+++ incubator/shindig/trunk/java/gadgets/src/test/java/org/apache/shindig/gadgets/servlet/ProxyBaseTest.java Wed Jun 11 02:33:22 2008
@@ -0,0 +1,212 @@
+/*
+ * 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.servlet;
+
+import static junitx.framework.StringAssert.assertContains;
+import static org.easymock.EasyMock.expect;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+
+import org.apache.shindig.gadgets.GadgetException;
+import org.apache.shindig.gadgets.http.HttpResponse;
+
+import com.google.common.collect.Maps;
+
+import org.junit.Test;
+
+import java.net.URI;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Tests for ProxyBase.
+ */
+public class ProxyBaseTest {
+
+ private final ServletTestFixture fixture = new ServletTestFixture();
+
+ private final ProxyBase proxy = new ProxyBase() {
+ @Override
+ public void fetch(HttpServletRequest request, HttpServletResponse response) {
+ // Nothing.
+ }
+ };
+
+ @Test
+ public void validateUrlNoPath() throws Exception {
+ URI url = proxy.validateUrl("http://www.example.com");
+ assertEquals("http", url.getScheme());
+ assertEquals("www.example.com", url.getHost());
+ assertEquals(-1, url.getPort());
+ assertEquals("/", url.getPath());
+ assertNull(url.getQuery());
+ assertNull(url.getFragment());
+ }
+
+ @Test
+ public void validateUrlHttps() throws Exception {
+ URI url = proxy.validateUrl("https://www.example.com");
+ assertEquals("https", url.getScheme());
+ assertEquals("www.example.com", url.getHost());
+ assertEquals(-1, url.getPort());
+ assertEquals("/", url.getPath());
+ assertNull(url.getQuery());
+ assertNull(url.getFragment());
+ }
+
+ @Test
+ public void validateUrlWithPath() throws Exception {
+ URI url = proxy.validateUrl("http://www.example.com/foo");
+ assertEquals("http", url.getScheme());
+ assertEquals("www.example.com", url.getHost());
+ assertEquals(-1, url.getPort());
+ assertEquals("/foo", url.getPath());
+ assertNull(url.getQuery());
+ assertNull(url.getFragment());
+ }
+
+ @Test
+ public void validateUrlWithPort() throws Exception {
+ URI url = proxy.validateUrl("http://www.example.com:8080/foo");
+ assertEquals("http", url.getScheme());
+ assertEquals("www.example.com", url.getHost());
+ assertEquals(8080, url.getPort());
+ assertEquals("/foo", url.getPath());
+ assertNull(url.getQuery());
+ assertNull(url.getFragment());
+ }
+
+ @Test
+ public void validateUrlWithEncodedPath() throws Exception {
+ URI url = proxy.validateUrl("http://www.example.com:8080/foo%20bar");
+ assertEquals("http", url.getScheme());
+ assertEquals("www.example.com", url.getHost());
+ assertEquals(8080, url.getPort());
+ assertEquals("/foo%20bar", url.getRawPath());
+ assertEquals("/foo bar", url.getPath());
+ assertNull(url.getQuery());
+ assertNull(url.getFragment());
+ }
+
+ @Test
+ public void validateUrlWithEncodedQuery() throws Exception {
+ URI url = proxy.validateUrl("http://www.example.com:8080/foo?q=with%20space");
+ assertEquals("http", url.getScheme());
+ assertEquals("www.example.com", url.getHost());
+ assertEquals(8080, url.getPort());
+ assertEquals("/foo", url.getPath());
+ assertEquals("q=with%20space", url.getRawQuery());
+ assertEquals("q=with space", url.getQuery());
+ assertNull(url.getFragment());
+ }
+
+ @Test
+ public void validateUrlWithNoPathAndEncodedQuery() throws Exception {
+ URI url = proxy.validateUrl("http://www.example.com?q=with%20space");
+ assertEquals("http", url.getScheme());
+ assertEquals("www.example.com", url.getHost());
+ assertEquals(-1, url.getPort());
+ assertEquals("/", url.getPath());
+ assertEquals("q=with%20space", url.getRawQuery());
+ assertEquals("q=with space", url.getQuery());
+ assertNull(url.getFragment());
+ }
+
+ @Test(expected = GadgetException.class)
+ public void validateUrlNullInput() throws GadgetException {
+ proxy.validateUrl(null);
+ }
+
+ @Test(expected = GadgetException.class)
+ public void validateUrlBadInput() throws GadgetException {
+ proxy.validateUrl("%$#%#$%#$%");
+ }
+
+ @Test(expected = GadgetException.class)
+ public void validateUrlBadProtocol() throws GadgetException {
+ proxy.validateUrl("gopher://foo");
+ }
+
+ @Test
+ public void setResponseHeaders() {
+ HttpResponse results = new HttpResponse(HttpResponse.SC_OK);
+ HttpServletResponseRecorder recorder = new HttpServletResponseRecorder(fixture.response);
+ fixture.replay();
+
+ proxy.setResponseHeaders(fixture.request, recorder, results);
+
+ // Just verify that they were set. Specific values are configurable.
+ assertNotNull("Expires header not set", recorder.getHeader("Expires"));
+ assertNotNull("Cache-Control header not set", recorder.getHeader("Cache-Control"));
+ assertEquals("attachment;filename=p.txt", recorder.getHeader("Content-Disposition"));
+ }
+
+ @Test
+ public void setResponseHeadersNoCache() {
+ Map<String, List<String>> headers = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER);
+ headers.put("Pragma", Arrays.asList("no-cache"));
+ HttpResponse results = new HttpResponse(HttpResponse.SC_OK, new byte[0], headers);
+ HttpServletResponseRecorder recorder = new HttpServletResponseRecorder(fixture.response);
+ fixture.replay();
+
+ proxy.setResponseHeaders(fixture.request, recorder, results);
+
+ // Just verify that they were set. Specific values are configurable.
+ assertNotNull("Expires header not set", recorder.getHeader("Expires"));
+ assertEquals("no-cache", recorder.getHeader("Pragma"));
+ assertEquals("no-cache", recorder.getHeader("Cache-Control"));
+ assertEquals("attachment;filename=p.txt", recorder.getHeader("Content-Disposition"));
+ }
+
+ @Test
+ public void setResponseHeadersForceParam() {
+ HttpResponse results = new HttpResponse(HttpResponse.SC_OK);
+ HttpServletResponseRecorder recorder = new HttpServletResponseRecorder(fixture.response);
+ expect(fixture.request.getParameter(ProxyBase.REFRESH_PARAM)).andReturn("30").anyTimes();
+ fixture.replay();
+
+ proxy.setResponseHeaders(fixture.request, recorder, results);
+
+ // Just verify that they were set. Specific values are configurable.
+ assertNotNull("Expires header not set", recorder.getHeader("Expires"));
+ assertContains("30", recorder.getHeader("Cache-Control"));
+ assertEquals("attachment;filename=p.txt", recorder.getHeader("Content-Disposition"));
+ }
+
+ @Test
+ public void getParameter() {
+ expect(fixture.request.getParameter("foo")).andReturn("bar");
+ fixture.replay();
+
+ assertEquals("bar", proxy.getParameter(fixture.request, "foo", "not foo"));
+ }
+
+ @Test
+ public void getParameterWithNullValue() {
+ expect(fixture.request.getParameter("foo")).andReturn(null);
+ fixture.replay();
+
+ assertEquals("not foo", proxy.getParameter(fixture.request, "foo", "not foo"));
+ }
+}