| /* |
| * 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. |
| * <p> |
| * 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<? extends Map.Entry> parameters) { |
| this.method = method; |
| this.URL = URL; |
| if (parameters == null) { |
| this.parameters = new ArrayList<Map.Entry<String, String>>(); |
| } else { |
| this.parameters = new ArrayList<Map.Entry<String, String>>(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<Map.Entry<String, String>> parameters; |
| private Map<String, String> parameterMap; |
| private boolean parametersAreComplete = false; |
| private final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>(); |
| |
| 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<Map.Entry<String, String>> 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<String, String> parameter) { |
| parameters.add(parameter); |
| parameterMap = null; |
| } |
| |
| public void addParameters( |
| Collection<? extends Map.Entry<String, String>> 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<String, String> 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<String, String> 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<Map.Entry<String, String>> 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<String, Object> getDump() throws IOException { |
| Map<String, Object> into = new HashMap<String, Object>(); |
| dump(into); |
| return into; |
| } |
| |
| protected void dump(Map<String, Object> 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<String> present = getParameterMap().keySet(); |
| List<String> absent = new ArrayList<String>(); |
| 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<String, String> 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<OAuth.Parameter> decodeAuthorization(String authorization) { |
| List<OAuth.Parameter> into = new ArrayList<OAuth.Parameter>(); |
| 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(); |
| } |
| |
| } |