Java tutorial
/* * 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 com.google.inject.Inject; import com.google.inject.Singleton; import org.apache.commons.lang.StringUtils; import org.apache.shindig.auth.AuthInfo; import org.apache.shindig.auth.SecurityToken; import org.apache.shindig.common.JsonSerializer; import org.apache.shindig.common.servlet.HttpUtil; import org.apache.shindig.common.uri.Uri; import org.apache.shindig.common.util.Utf8UrlCoder; import org.apache.shindig.config.ContainerConfig; import org.apache.shindig.gadgets.AuthType; import org.apache.shindig.gadgets.FeedProcessor; import org.apache.shindig.gadgets.FetchResponseUtils; import org.apache.shindig.gadgets.GadgetException; import org.apache.shindig.gadgets.GadgetException.Code; import org.apache.shindig.gadgets.http.HttpRequest; import org.apache.shindig.gadgets.http.HttpResponse; import org.apache.shindig.gadgets.http.RequestPipeline; import org.apache.shindig.gadgets.oauth.OAuthArguments; import org.apache.shindig.gadgets.rewrite.ResponseRewriterRegistry; import org.apache.shindig.gadgets.rewrite.RewritingException; import org.apache.shindig.gadgets.uri.UriCommon; import org.apache.shindig.gadgets.uri.UriCommon.Param; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.util.Collections; 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. */ @Singleton public class MakeRequestHandler { // Relaxed visibility for ease of integration. Try to avoid relying on these. 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 HEADERS_PARAM = "headers"; 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 static final String GET_FULL_HEADERS_PARAM = "getFullHeaders"; public static final String AUTHZ_PARAM = "authz"; private final RequestPipeline requestPipeline; private final ResponseRewriterRegistry contentRewriterRegistry; @Inject public MakeRequestHandler(RequestPipeline requestPipeline, ResponseRewriterRegistry contentRewriterRegistry) { this.requestPipeline = requestPipeline; this.contentRewriterRegistry = contentRewriterRegistry; } /** * Executes a request, returning the response as JSON to be handled by makeRequest. */ public void fetch(HttpServletRequest request, HttpServletResponse response) throws GadgetException, IOException { HttpRequest rcr = buildHttpRequest(request); // Serialize the response HttpResponse results = requestPipeline.execute(rcr); // Rewrite the response if (contentRewriterRegistry != null) { try { results = contentRewriterRegistry.rewriteHttpResponse(rcr, results); } catch (RewritingException e) { throw new GadgetException(GadgetException.Code.INTERNAL_SERVER_ERROR, e, e.getHttpStatusCode()); } } // Serialize the response String output = convertResponseToJson(rcr.getSecurityToken(), request, results); // Find and set the refresh interval setResponseHeaders(request, response, results); response.setStatus(HttpServletResponse.SC_OK); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); response.getWriter().write(UNPARSEABLE_CRUFT + output); } /** * Generate a remote content request based on the parameters * sent from the client. * @throws GadgetException */ protected HttpRequest buildHttpRequest(HttpServletRequest request) throws GadgetException { String urlStr = request.getParameter(Param.URL.getKey()); if (urlStr == null) { throw new GadgetException(GadgetException.Code.INVALID_PARAMETER, Param.URL.getKey() + " parameter is missing.", HttpResponse.SC_BAD_REQUEST); } Uri url = null; try { url = ServletUtil.validateUrl(Uri.parse(urlStr)); } catch (IllegalArgumentException e) { throw new GadgetException(GadgetException.Code.INVALID_PARAMETER, "Invalid " + Param.URL.getKey() + " parameter", HttpResponse.SC_BAD_REQUEST); } HttpRequest req = new HttpRequest(url).setMethod(getParameter(request, METHOD_PARAM, "GET")) .setContainer(getContainer(request)); setPostData(request, req); String headerData = getParameter(request, HEADERS_PARAM, ""); if (headerData.length() > 0) { String[] headerList = StringUtils.split(headerData, '&'); for (String header : headerList) { String[] parts = StringUtils.splitPreserveAllTokens(header, '='); if (parts.length != 2) { throw new GadgetException(GadgetException.Code.INVALID_PARAMETER, "Malformed header param specified:" + header, HttpResponse.SC_BAD_REQUEST); } String headerName = Utf8UrlCoder.decode(parts[0]); if (!HttpRequestHandler.BAD_HEADERS.contains(headerName.toUpperCase())) { req.addHeader(headerName, Utf8UrlCoder.decode(parts[1])); } } } // Set the default content type for post requests when a content type is not specified if ("POST".equals(req.getMethod()) && req.getHeader("Content-Type") == null) { req.addHeader("Content-Type", "application/x-www-form-urlencoded"); } req.setIgnoreCache("1".equals(request.getParameter(Param.NO_CACHE.getKey()))); if (request.getParameter(Param.GADGET.getKey()) != null) { req.setGadget(Uri.parse(request.getParameter(Param.GADGET.getKey()))); } // If the proxy request specifies a refresh param then we want to force the min TTL for // the retrieved entry in the cache regardless of the headers on the content when it // is fetched from the original source. if (request.getParameter(Param.REFRESH.getKey()) != null) { try { req.setCacheTtl(Integer.parseInt(request.getParameter(Param.REFRESH.getKey()))); } catch (NumberFormatException nfe) { // Ignore } } // 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 req.setRewriteMimeType(request.getParameter(Param.REWRITE_MIME_TYPE.getKey())); // Figure out whether authentication is required AuthType auth = AuthType.parse(getParameter(request, AUTHZ_PARAM, null)); req.setAuthType(auth); if (auth != AuthType.NONE) { req.setSecurityToken(extractAndValidateToken(request)); req.setOAuthArguments(new OAuthArguments(auth, request)); } ServletUtil.setXForwardedForHeader(request, req); return req; } /** * Set http request post data according to servlet request. * It uses header encoding if available, and defaulted to utf8 * Override the function if different behavior is needed. */ protected void setPostData(HttpServletRequest request, HttpRequest req) throws GadgetException { String encoding = request.getCharacterEncoding(); if (encoding == null) { encoding = "UTF-8"; } try { req.setPostBody(getParameter(request, POST_DATA_PARAM, "").getBytes(encoding.toUpperCase())); } catch (UnsupportedEncodingException e) { // We might consider enumerating at least a small list of encodings // that we must always honor. For now, we return SC_BAD_REQUEST since // the encoding parameter could theoretically be anything. throw new GadgetException(Code.HTML_PARSE_ERROR, e, HttpResponse.SC_BAD_REQUEST); } } /** * Format a response as JSON, including additional JSON inserted by * chained content fetchers. */ protected String convertResponseToJson(SecurityToken authToken, HttpServletRequest request, HttpResponse results) throws GadgetException { boolean getFullHeaders = Boolean.parseBoolean(getParameter(request, GET_FULL_HEADERS_PARAM, "false")); String originalUrl = request.getParameter(Param.URL.getKey()); String body = results.getResponseAsString(); if (body.length() > 0) { if ("FEED".equals(request.getParameter(CONTENT_TYPE_PARAM))) { body = processFeed(originalUrl, request, body); } } Map<String, Object> resp = FetchResponseUtils.getResponseAsJson(results, null, body, getFullHeaders); if (authToken != null) { String updatedAuthToken = authToken.getUpdatedToken(); if (updatedAuthToken != null) { resp.put("st", updatedAuthToken); } } // Use raw param as key as URL may have to be decoded return JsonSerializer.serialize(Collections.singletonMap(originalUrl, resp)); } protected RequestPipeline getRequestPipeline() { return requestPipeline; } /** * @param request * @return A valid token for the given input. */ private SecurityToken extractAndValidateToken(HttpServletRequest request) throws GadgetException { SecurityToken token = new AuthInfo(request).getSecurityToken(); if (token == null) { // TODO: Determine appropriate external error code for this. throw new GadgetException(GadgetException.Code.INVALID_SECURITY_TOKEN); } return token; } /** * 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; try { numEntries = Integer.valueOf(getParameter(req, NUM_ENTRIES_PARAM, DEFAULT_NUM_ENTRIES)); } catch (NumberFormatException e) { throw new GadgetException(GadgetException.Code.INVALID_PARAMETER, "numEntries paramater is not a number", HttpResponse.SC_BAD_REQUEST); } return new FeedProcessor().process(url, xml, getSummaries, numEntries).toString(); } /** * Extracts the container name from the request. */ @SuppressWarnings("deprecation") protected static String getContainer(HttpServletRequest request) { String container = request.getParameter(Param.CONTAINER.getKey()); if (container == null) { container = request.getParameter(Param.SYND.getKey()); } return container != null ? container : ContainerConfig.DEFAULT_CONTAINER; } /** * getParameter helper method, returning default value if param not present. */ protected static String getParameter(HttpServletRequest request, String key, String defaultValue) { String ret = request.getParameter(key); return ret != null ? ret : defaultValue; } /** * Sets cache control headers for the response. */ @SuppressWarnings("boxing") protected static void setResponseHeaders(HttpServletRequest request, HttpServletResponse response, HttpResponse results) throws GadgetException { int refreshInterval = 0; if (results.isStrictNoCache() || "1".equals(request.getParameter(UriCommon.Param.NO_CACHE.getKey()))) { refreshInterval = 0; } else if (request.getParameter(UriCommon.Param.REFRESH.getKey()) != null) { try { refreshInterval = Integer.valueOf(request.getParameter(UriCommon.Param.REFRESH.getKey())); } catch (NumberFormatException nfe) { throw new GadgetException(GadgetException.Code.INVALID_PARAMETER, "refresh parameter is not a number", HttpResponse.SC_BAD_REQUEST); } } else { refreshInterval = Math.max(60 * 60, (int) (results.getCacheTtl() / 1000L)); } HttpUtil.setCachingHeaders(response, refreshInterval, false); // Always set Content-Disposition header as XSS prevention mechanism. response.setHeader("Content-Disposition", "attachment;filename=p.txt"); if (response.getContentType() == null) { response.setContentType("application/octet-stream"); } } }