/* * 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.http; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.ArrayUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.shindig.auth.SecurityToken; import org.apache.shindig.common.uri.Uri; import org.apache.shindig.config.ContainerConfig; import org.apache.shindig.gadgets.AuthType; import org.apache.shindig.gadgets.oauth.OAuthArguments; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; /** * Creates HttpRequests. A new HttpRequest should be created for every unique HttpRequest * being constructed. */ public class HttpRequest { /** Automatically added to every request so that we know that the request came from our server. */ public static final String DOS_PREVENTION_HEADER = "X-shindig-dos"; static final String DEFAULT_CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"; private String method = "GET"; private Uri uri; private final Map<String, List<String>> headers = Maps.newTreeMap(String.CASE_INSENSITIVE_ORDER); // Internal parameters which serve as extra information to pass along the // chain of HttpRequest processing. // NOTE: These are not get/post parameter equivalent of HttpServletRequest. private final Map<String, String> params = Maps.newHashMap(); private byte[] postBody = ArrayUtils.EMPTY_BYTE_ARRAY; // TODO: It might be useful to refactor these into a simple map of objects and use sub classes // for more detailed data. // Cache control. private boolean ignoreCache; private int cacheTtl = -1; // Sanitization private boolean sanitizationRequested; // Caja private boolean cajaRequested; // Whether to follow redirects private boolean followRedirects = true; // Context for the request. private Uri gadget; private String container = ContainerConfig.DEFAULT_CONTAINER; // For signed fetch & OAuth private SecurityToken securityToken; // TODO: Move this into OAuthRequest. private OAuthArguments oauthArguments; private AuthType authType; private String rewriteMimeType; /** * Construct a new request for the given uri. */ public HttpRequest(Uri uri) { this.uri = uri; authType = AuthType.NONE; addHeader(DOS_PREVENTION_HEADER, "on"); } /** * Clone an existing HttpRequest. */ public HttpRequest(HttpRequest request) { method = request.method; uri = request.uri; headers.putAll(request.headers); postBody = request.postBody; ignoreCache = request.ignoreCache; cacheTtl = request.cacheTtl; gadget = request.gadget; container = request.container; securityToken = request.securityToken; if (request.oauthArguments != null) { oauthArguments = new OAuthArguments(request.oauthArguments); } authType = request.authType; rewriteMimeType = request.rewriteMimeType; followRedirects = request.followRedirects; } public HttpRequest setMethod(String method) { this.method = method; return this; } public HttpRequest setUri(Uri uri) { this.uri = uri; return this; } /** * Add a single header to the request. If a value for the given name is already set, a second * value is added. If you wish to overwrite any possible values for a header, use * {@link #setHeader(String, String)}. */ public HttpRequest addHeader(String name, String value) { List<String> values = headers.get(name); if (values == null) { values = Lists.newArrayList(); headers.put(name, values); } values.add(value); return this; } /** * Sets a single header value, overwriting any previously set headers with the same name. */ public HttpRequest setHeader(String name, String value) { headers.put(name, Lists.newArrayList(value)); return this; } /** * Adds an entire map of headers to the request. */ public HttpRequest addHeaders(Map<String, String> headers) { for (Map.Entry<String, String> entry : headers.entrySet()) { addHeader(entry.getKey(), entry.getValue()); } return this; } /** * Adds all headers in the provided map to the request. */ public HttpRequest addAllHeaders(Map<String, ? extends List<String>> headers) { this.headers.putAll(headers); return this; } /** * Remove all headers with the given name from the request. * * @return Any values that were removed from the request. */ public List<String> removeHeader(String name) { return headers.remove(name); } /** * Assigns the specified body to the request, copying all input bytes. */ public HttpRequest setPostBody(byte[] postBody) { if (postBody == null) { this.postBody = ArrayUtils.EMPTY_BYTE_ARRAY; } else { this.postBody = new byte[postBody.length]; System.arraycopy(postBody, 0, this.postBody, 0, postBody.length); } return this; } /** * Fills in the request body from an InputStream. */ public HttpRequest setPostBody(InputStream is) throws IOException { postBody = IOUtils.toByteArray(is); return this; } /** * @param ignoreCache Whether to ignore all caching for this request. */ public HttpRequest setIgnoreCache(boolean ignoreCache) { this.ignoreCache = ignoreCache; if (ignoreCache) { // Bypass any proxy caches as well. headers.put("Pragma", Lists.newArrayList("no-cache")); } return this; } /** * Should content fetched in response to this request * be sanitized based on the specified mime-type */ public boolean isSanitizationRequested() { return sanitizationRequested; } public void setSanitizationRequested(boolean sanitizationRequested) { this.sanitizationRequested = sanitizationRequested; } /** * Should content fetched in response to this request * be sanitized based on the specified mime-type */ public boolean isCajaRequested() { return cajaRequested; } public void setCajaRequested(boolean cajaRequested) { this.cajaRequested = cajaRequested; } /** * @param cacheTtl The amount of time to cache the result object for, in seconds. If set to -1, * HTTP cache control headers will be honored. Otherwise objects will be cached for the time * specified. */ public HttpRequest setCacheTtl(int cacheTtl) { this.cacheTtl = cacheTtl; return this; } /** * @param gadget The gadget that caused this HTTP request to be necessary. May be null if the * request was not initiated by the actions of a gadget. */ public HttpRequest setGadget(Uri gadget) { this.gadget = gadget; return this; } /** * @param container The container that this request originated from. */ public HttpRequest setContainer(String container) { this.container = container; return this; } /** * Assign the security token to use for making any form of authenticated request. */ public HttpRequest setSecurityToken(SecurityToken securityToken) { this.securityToken = securityToken; return this; } /** * @param oauthArguments arguments for OAuth/signed fetched */ public HttpRequest setOAuthArguments(OAuthArguments oauthArguments) { this.oauthArguments = oauthArguments; return this; } /** * @param followRedirects whether this request should automatically follow redirects. */ public HttpRequest setFollowRedirects(boolean followRedirects) { this.followRedirects = followRedirects; return this; } /** * @param authType The type of authentication being used for this request. */ public HttpRequest setAuthType(AuthType authType) { this.authType = authType; return this; } /** * @param rewriteMimeType The assumed content type of the response to be rewritten. Overrides * any values set in the Content-Type response header. * * TODO: Move this to new rewriting facility. */ public HttpRequest setRewriteMimeType(String rewriteMimeType) { this.rewriteMimeType = rewriteMimeType; return this; } public String getMethod() { return method; } public Uri getUri() { return uri; } /** * @return All headers to be sent in this request. */ public Map<String, List<String>> getHeaders() { return headers; } /** * @param name The header to fetch * @return A list of headers with that name (may be empty). */ public List<String> getHeaders(String name) { List<String> match = headers.get(name); if (match == null) { return Collections.emptyList(); } else { return match; } } /** * @return The first set header with the given name or null if not set. If * you need multiple values for the header, use getHeaders(). */ public String getHeader(String name) { List<String> headerList = getHeaders(name); if (headerList.isEmpty()) { return null; } else { return headerList.get(0); } } /** * @return The content type of the request (determined from request headers) */ public String getContentType() { String type = getHeader("Content-Type"); if (type == null) { return DEFAULT_CONTENT_TYPE; } return type; } /** * @return An input stream that can be used to read the post body. */ public InputStream getPostBody() { return new ByteArrayInputStream(postBody); } /** * @return The post body as a string, assuming UTF-8 encoding. * TODO: We should probably tolerate other encodings, based on the * Content-Type header. */ public String getPostBodyAsString() { try { return new String(postBody, "UTF-8"); } catch (UnsupportedEncodingException ignore) { return ""; } } /** * Retrieves the total length of the post body. * * @return The length of the post body. */ public int getPostBodyLength() { return postBody.length; } /** * @return True if caching should be ignored for this request. */ public boolean getIgnoreCache() { return ignoreCache; } /** * @return The amount of time to cache any response objects for, in seconds. */ public int getCacheTtl() { return cacheTtl; } /** * @return The uri of gadget responsible for making this request. */ public Uri getGadget() { return gadget; } public String getParam(String paramName) { return params.get(paramName); } public Integer getParamAsInteger(String paramName) { String value = params.get(paramName); if (value == null) { return null; } return NumberUtils.createInteger(value); } public <T> void setParam(String paramName, T paramValue) { params.put(paramName, (paramValue == null) ? null : String.valueOf(paramValue)); } public Map<String, String> getParams() { return params; } /** * @return The container responsible for making this request. */ public String getContainer() { return container; } /** * @return The security token used to make this request. */ public SecurityToken getSecurityToken() { return securityToken; } /** * @return arguments for OAuth and signed fetch */ public OAuthArguments getOAuthArguments() { return oauthArguments; } /** * @return true if redirects should be followed. */ public boolean getFollowRedirects() { return followRedirects; } /** * @return The type of authentication being used for this request. */ public AuthType getAuthType() { return authType; } /** * @return The content type to assume when rewriting. * * TODO: Move this to new rewriting facility. */ public String getRewriteMimeType() { return rewriteMimeType; } @Override public String toString() { StringBuilder buf = new StringBuilder(method); buf.append(' ').append(uri.getPath()) .append(uri.getQuery() == null ? "" : '?' + uri.getQuery()).append("\n\n"); buf.append("Host: ").append(uri.getAuthority()).append('\n'); buf.append("X-Shindig-AuthType: ").append(authType).append('\n'); for (Map.Entry<String, List<String>> entry : headers.entrySet()) { String name = entry.getKey(); for (String value : entry.getValue()) { buf.append(name).append(": ").append(value).append('\n'); } } buf.append('\n'); buf.append(getPostBodyAsString()); return buf.toString(); } @Override public int hashCode() { return method.hashCode() ^ uri.hashCode() ^ authType.hashCode() ^ Arrays.hashCode(postBody) ^ headers.hashCode(); } @Override public boolean equals(Object obj) { if (obj == this) { return true; } if (!(obj instanceof HttpRequest)) { return false; } HttpRequest req = (HttpRequest)obj; return method.equals(req.method) && uri.equals(req.uri) && authType == req.authType && Arrays.equals(postBody, req.postBody) && headers.equals(req.headers); // TODO: Verify that other fields aren't meaningful. Especially important to check for oauth args. } }