/* * Copyright 2007, 2008 Netflix, Inc. * * Licensed 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 net.oauth; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.Reader; import java.net.URISyntaxException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import net.oauth.http.HttpMessage; import net.oauth.signature.OAuthSignatureMethod; /** * A request or response message used in the OAuth protocol. *

* The parameters in this class are not percent-encoded. Methods like * OAuthClient.invoke and OAuthResponseMessage.completeParameters are * responsible for percent-encoding parameters before transmission and decoding * them after reception. * * @author John Kristian * @hide */ public class OAuthMessage { public OAuthMessage(String method, String URL, Collection parameters) { this.method = method; this.URL = URL; if (parameters == null) { this.parameters = new ArrayList>(); } else { this.parameters = new ArrayList>(parameters.size()); for (Map.Entry p : parameters) { this.parameters.add(new OAuth.Parameter( toString(p.getKey()), toString(p.getValue()))); } } } public String method; public String URL; private final List> parameters; private Map parameterMap; private boolean parametersAreComplete = false; private final List> headers = new ArrayList>(); public String toString() { return "OAuthMessage(" + method + ", " + URL + ", " + parameters + ")"; } /** A caller is about to get a parameter. */ private void beforeGetParameter() throws IOException { if (!parametersAreComplete) { completeParameters(); parametersAreComplete = true; } } /** * Finish adding parameters; for example read an HTTP response body and * parse parameters from it. */ protected void completeParameters() throws IOException { } public List> getParameters() throws IOException { beforeGetParameter(); return Collections.unmodifiableList(parameters); } public void addParameter(String key, String value) { addParameter(new OAuth.Parameter(key, value)); } public void addParameter(Map.Entry parameter) { parameters.add(parameter); parameterMap = null; } public void addParameters( Collection> parameters) { this.parameters.addAll(parameters); parameterMap = null; } public String getParameter(String name) throws IOException { return getParameterMap().get(name); } public String getConsumerKey() throws IOException { return getParameter(OAuth.OAUTH_CONSUMER_KEY); } public String getToken() throws IOException { return getParameter(OAuth.OAUTH_TOKEN); } public String getSignatureMethod() throws IOException { return getParameter(OAuth.OAUTH_SIGNATURE_METHOD); } public String getSignature() throws IOException { return getParameter(OAuth.OAUTH_SIGNATURE); } protected Map getParameterMap() throws IOException { beforeGetParameter(); if (parameterMap == null) { parameterMap = OAuth.newMap(parameters); } return parameterMap; } /** * The MIME type of the body of this message. * * @return the MIME type, or null to indicate the type is unknown. */ public String getBodyType() { return getHeader(HttpMessage.CONTENT_TYPE); } /** * The character encoding of the body of this message. * * @return the name of an encoding, or "ISO-8859-1" if no charset has been * specified. */ public String getBodyEncoding() { return HttpMessage.DEFAULT_CHARSET; } /** * The value of the last HTTP header with the given name. The name is case * insensitive. * * @return the value of the last header, or null to indicate that there is * no such header in this message. */ public final String getHeader(String name) { String value = null; // no such header for (Map.Entry header : getHeaders()) { if (name.equalsIgnoreCase(header.getKey())) { value = header.getValue(); } } return value; } /** All HTTP headers. You can add headers to this list. */ public final List> getHeaders() { return headers; } /** * Read the body of the HTTP request or response and convert it to a String. * This method isn't repeatable, since it consumes and closes getBodyAsStream. * * @return the body, or null to indicate there is no body. */ public final String readBodyAsString() throws IOException { InputStream body = getBodyAsStream(); return readAll(body, getBodyEncoding()); } /** * Get a stream from which to read the body of the HTTP request or response. * This is designed to support efficient streaming of a large message. * The caller must close the returned stream, to release the underlying * resources such as the TCP connection for an HTTP response. * * @return a stream from which to read the body, or null to indicate there * is no body. */ public InputStream getBodyAsStream() throws IOException { return null; } /** Construct a verbose description of this message and its origins. */ public Map getDump() throws IOException { Map into = new HashMap(); dump(into); return into; } protected void dump(Map into) throws IOException { into.put("URL", URL); if (parametersAreComplete) { try { into.putAll(getParameterMap()); } catch (Exception ignored) { } } } /** * Verify that the required parameter names are contained in the actual * collection. * * @throws OAuthProblemException * one or more parameters are absent. * @throws IOException */ public void requireParameters(String... names) throws OAuthProblemException, IOException { Set present = getParameterMap().keySet(); List absent = new ArrayList(); for (String required : names) { if (!present.contains(required)) { absent.add(required); } } if (!absent.isEmpty()) { OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.PARAMETER_ABSENT); problem.setParameter(OAuth.Problems.OAUTH_PARAMETERS_ABSENT, OAuth.percentEncode(absent)); throw problem; } } /** * Add some of the parameters needed to request access to a protected * resource, if they aren't already in the message. * * @throws IOException * @throws URISyntaxException */ public void addRequiredParameters(OAuthAccessor accessor) throws OAuthException, IOException, URISyntaxException { final Map pMap = OAuth.newMap(parameters); if (pMap.get(OAuth.OAUTH_TOKEN) == null && accessor.accessToken != null) { addParameter(OAuth.OAUTH_TOKEN, accessor.accessToken); } final OAuthConsumer consumer = accessor.consumer; if (pMap.get(OAuth.OAUTH_CONSUMER_KEY) == null) { addParameter(OAuth.OAUTH_CONSUMER_KEY, consumer.consumerKey); } String signatureMethod = pMap.get(OAuth.OAUTH_SIGNATURE_METHOD); if (signatureMethod == null) { signatureMethod = (String) consumer.getProperty(OAuth.OAUTH_SIGNATURE_METHOD); if (signatureMethod == null) { signatureMethod = OAuth.HMAC_SHA1; } addParameter(OAuth.OAUTH_SIGNATURE_METHOD, signatureMethod); } if (pMap.get(OAuth.OAUTH_TIMESTAMP) == null) { addParameter(OAuth.OAUTH_TIMESTAMP, (System.currentTimeMillis() / 1000) + ""); } if (pMap.get(OAuth.OAUTH_NONCE) == null) { addParameter(OAuth.OAUTH_NONCE, System.nanoTime() + ""); } if (pMap.get(OAuth.OAUTH_VERSION) == null) { addParameter(OAuth.OAUTH_VERSION, OAuth.VERSION_1_0); } this.sign(accessor); } /** * Add a signature to the message. * * @throws URISyntaxException */ public void sign(OAuthAccessor accessor) throws IOException, OAuthException, URISyntaxException { OAuthSignatureMethod.newSigner(this, accessor).sign(this); } /** * Check that the message is valid. * * @throws IOException * @throws URISyntaxException * * @throws OAuthProblemException * the message is invalid */ public void validateMessage(OAuthAccessor accessor, OAuthValidator validator) throws OAuthException, IOException, URISyntaxException { validator.validateMessage(this, accessor); } /** * Construct a WWW-Authenticate or Authentication header value, containing * the given realm plus all the parameters whose names begin with "oauth_". */ public String getAuthorizationHeader(String realm) throws IOException { StringBuilder into = new StringBuilder(); if (realm != null) { into.append(" realm=\"").append(OAuth.percentEncode(realm)).append('"'); } beforeGetParameter(); if (parameters != null) { for (Map.Entry parameter : parameters) { String name = toString(parameter.getKey()); if (name.startsWith("oauth_")) { if (into.length() > 0) into.append(","); into.append(" "); into.append(OAuth.percentEncode(name)).append("=\""); into.append(OAuth.percentEncode(toString(parameter.getValue()))).append('"'); } } } return AUTH_SCHEME + into.toString(); } /** * Read all the data from the given stream, and close it. * * @return null if from is null, or the data from the stream converted to a * String */ public static String readAll(InputStream from, String encoding) throws IOException { if (from == null) { return null; } try { StringBuilder into = new StringBuilder(); Reader r = new InputStreamReader(from, encoding); char[] s = new char[512]; for (int n; 0 < (n = r.read(s));) { into.append(s, 0, n); } return into.toString(); } finally { from.close(); } } /** * Parse the parameters from an OAuth Authorization or WWW-Authenticate * header. The realm is included as a parameter. If the given header doesn't * start with "OAuth ", return an empty list. */ public static List decodeAuthorization(String authorization) { List into = new ArrayList(); if (authorization != null) { Matcher m = AUTHORIZATION.matcher(authorization); if (m.matches()) { if (AUTH_SCHEME.equalsIgnoreCase(m.group(1))) { for (String nvp : m.group(2).split("\\s*,\\s*")) { m = NVP.matcher(nvp); if (m.matches()) { String name = OAuth.decodePercent(m.group(1)); String value = OAuth.decodePercent(m.group(2)); into.add(new OAuth.Parameter(name, value)); } } } } } return into; } public static final String AUTH_SCHEME = "OAuth"; public static final String GET = "GET"; public static final String POST = "POST"; public static final String PUT = "PUT"; public static final String DELETE = "DELETE"; private static final Pattern AUTHORIZATION = Pattern.compile("\\s*(\\w*)\\s+(.*)"); private static final Pattern NVP = Pattern.compile("(\\S*)\\s*\\=\\s*\"([^\"]*)\""); private static final String toString(Object from) { return (from == null) ? null : from.toString(); } }