• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2007, 2008 Netflix, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package net.oauth;
18 
19 import java.io.IOException;
20 import java.io.InputStream;
21 import java.io.InputStreamReader;
22 import java.io.Reader;
23 import java.net.URISyntaxException;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Set;
31 import java.util.regex.Matcher;
32 import java.util.regex.Pattern;
33 import net.oauth.http.HttpMessage;
34 import net.oauth.signature.OAuthSignatureMethod;
35 
36 /**
37  * A request or response message used in the OAuth protocol.
38  * <p>
39  * The parameters in this class are not percent-encoded. Methods like
40  * OAuthClient.invoke and OAuthResponseMessage.completeParameters are
41  * responsible for percent-encoding parameters before transmission and decoding
42  * them after reception.
43  *
44  * @author John Kristian
45  * @hide
46  */
47 public class OAuthMessage {
48 
OAuthMessage(String method, String URL, Collection<? extends Map.Entry> parameters)49     public OAuthMessage(String method, String URL,
50             Collection<? extends Map.Entry> parameters) {
51         this.method = method;
52         this.URL = URL;
53         if (parameters == null) {
54             this.parameters = new ArrayList<Map.Entry<String, String>>();
55         } else {
56             this.parameters = new ArrayList<Map.Entry<String, String>>(parameters.size());
57             for (Map.Entry p : parameters) {
58                 this.parameters.add(new OAuth.Parameter(
59                         toString(p.getKey()), toString(p.getValue())));
60             }
61         }
62     }
63 
64     public String method;
65     public String URL;
66 
67     private final List<Map.Entry<String, String>> parameters;
68     private Map<String, String> parameterMap;
69     private boolean parametersAreComplete = false;
70     private final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>();
71 
toString()72     public String toString() {
73         return "OAuthMessage(" + method + ", " + URL + ", " + parameters + ")";
74     }
75 
76     /** A caller is about to get a parameter. */
beforeGetParameter()77     private void beforeGetParameter() throws IOException {
78         if (!parametersAreComplete) {
79             completeParameters();
80             parametersAreComplete = true;
81         }
82     }
83 
84     /**
85      * Finish adding parameters; for example read an HTTP response body and
86      * parse parameters from it.
87      */
completeParameters()88     protected void completeParameters() throws IOException {
89     }
90 
getParameters()91     public List<Map.Entry<String, String>> getParameters() throws IOException {
92         beforeGetParameter();
93         return Collections.unmodifiableList(parameters);
94     }
95 
addParameter(String key, String value)96     public void addParameter(String key, String value) {
97         addParameter(new OAuth.Parameter(key, value));
98     }
99 
addParameter(Map.Entry<String, String> parameter)100     public void addParameter(Map.Entry<String, String> parameter) {
101         parameters.add(parameter);
102         parameterMap = null;
103     }
104 
addParameters( Collection<? extends Map.Entry<String, String>> parameters)105     public void addParameters(
106             Collection<? extends Map.Entry<String, String>> parameters) {
107         this.parameters.addAll(parameters);
108         parameterMap = null;
109     }
110 
getParameter(String name)111     public String getParameter(String name) throws IOException {
112         return getParameterMap().get(name);
113     }
114 
getConsumerKey()115     public String getConsumerKey() throws IOException {
116         return getParameter(OAuth.OAUTH_CONSUMER_KEY);
117     }
118 
getToken()119     public String getToken() throws IOException {
120         return getParameter(OAuth.OAUTH_TOKEN);
121     }
122 
getSignatureMethod()123     public String getSignatureMethod() throws IOException {
124         return getParameter(OAuth.OAUTH_SIGNATURE_METHOD);
125     }
126 
getSignature()127     public String getSignature() throws IOException {
128         return getParameter(OAuth.OAUTH_SIGNATURE);
129     }
130 
getParameterMap()131     protected Map<String, String> getParameterMap() throws IOException {
132         beforeGetParameter();
133         if (parameterMap == null) {
134             parameterMap = OAuth.newMap(parameters);
135         }
136         return parameterMap;
137     }
138 
139     /**
140      * The MIME type of the body of this message.
141      *
142      * @return the MIME type, or null to indicate the type is unknown.
143      */
getBodyType()144     public String getBodyType() {
145         return getHeader(HttpMessage.CONTENT_TYPE);
146     }
147 
148     /**
149      * The character encoding of the body of this message.
150      *
151      * @return the name of an encoding, or "ISO-8859-1" if no charset has been
152      *         specified.
153      */
getBodyEncoding()154     public String getBodyEncoding() {
155         return HttpMessage.DEFAULT_CHARSET;
156     }
157 
158     /**
159      * The value of the last HTTP header with the given name. The name is case
160      * insensitive.
161      *
162      * @return the value of the last header, or null to indicate that there is
163      *         no such header in this message.
164      */
getHeader(String name)165     public final String getHeader(String name) {
166         String value = null; // no such header
167         for (Map.Entry<String, String> header : getHeaders()) {
168             if (name.equalsIgnoreCase(header.getKey())) {
169                 value = header.getValue();
170             }
171         }
172         return value;
173     }
174 
175     /** All HTTP headers.  You can add headers to this list. */
getHeaders()176     public final List<Map.Entry<String, String>> getHeaders() {
177         return headers;
178     }
179 
180     /**
181      * Read the body of the HTTP request or response and convert it to a String.
182      * This method isn't repeatable, since it consumes and closes getBodyAsStream.
183      *
184      * @return the body, or null to indicate there is no body.
185      */
readBodyAsString()186     public final String readBodyAsString() throws IOException
187     {
188         InputStream body = getBodyAsStream();
189         return readAll(body, getBodyEncoding());
190     }
191 
192     /**
193      * Get a stream from which to read the body of the HTTP request or response.
194      * This is designed to support efficient streaming of a large message.
195      * The caller must close the returned stream, to release the underlying
196      * resources such as the TCP connection for an HTTP response.
197      *
198      * @return a stream from which to read the body, or null to indicate there
199      *         is no body.
200      */
getBodyAsStream()201     public InputStream getBodyAsStream() throws IOException {
202         return null;
203     }
204 
205     /** Construct a verbose description of this message and its origins. */
getDump()206     public Map<String, Object> getDump() throws IOException {
207         Map<String, Object> into = new HashMap<String, Object>();
208         dump(into);
209         return into;
210     }
211 
dump(Map<String, Object> into)212     protected void dump(Map<String, Object> into) throws IOException {
213         into.put("URL", URL);
214         if (parametersAreComplete) {
215             try {
216                 into.putAll(getParameterMap());
217             } catch (Exception ignored) {
218             }
219         }
220     }
221 
222     /**
223      * Verify that the required parameter names are contained in the actual
224      * collection.
225      *
226      * @throws OAuthProblemException
227      *                 one or more parameters are absent.
228      * @throws IOException
229      */
requireParameters(String... names)230     public void requireParameters(String... names)
231             throws OAuthProblemException, IOException {
232         Set<String> present = getParameterMap().keySet();
233         List<String> absent = new ArrayList<String>();
234         for (String required : names) {
235             if (!present.contains(required)) {
236                 absent.add(required);
237             }
238         }
239         if (!absent.isEmpty()) {
240             OAuthProblemException problem = new OAuthProblemException(OAuth.Problems.PARAMETER_ABSENT);
241             problem.setParameter(OAuth.Problems.OAUTH_PARAMETERS_ABSENT, OAuth.percentEncode(absent));
242             throw problem;
243         }
244     }
245 
246     /**
247      * Add some of the parameters needed to request access to a protected
248      * resource, if they aren't already in the message.
249      *
250      * @throws IOException
251      * @throws URISyntaxException
252      */
addRequiredParameters(OAuthAccessor accessor)253     public void addRequiredParameters(OAuthAccessor accessor)
254             throws OAuthException, IOException, URISyntaxException {
255         final Map<String, String> pMap = OAuth.newMap(parameters);
256         if (pMap.get(OAuth.OAUTH_TOKEN) == null && accessor.accessToken != null) {
257             addParameter(OAuth.OAUTH_TOKEN, accessor.accessToken);
258         }
259         final OAuthConsumer consumer = accessor.consumer;
260         if (pMap.get(OAuth.OAUTH_CONSUMER_KEY) == null) {
261             addParameter(OAuth.OAUTH_CONSUMER_KEY, consumer.consumerKey);
262         }
263         String signatureMethod = pMap.get(OAuth.OAUTH_SIGNATURE_METHOD);
264         if (signatureMethod == null) {
265             signatureMethod = (String) consumer.getProperty(OAuth.OAUTH_SIGNATURE_METHOD);
266             if (signatureMethod == null) {
267                 signatureMethod = OAuth.HMAC_SHA1;
268             }
269             addParameter(OAuth.OAUTH_SIGNATURE_METHOD, signatureMethod);
270         }
271         if (pMap.get(OAuth.OAUTH_TIMESTAMP) == null) {
272             addParameter(OAuth.OAUTH_TIMESTAMP, (System.currentTimeMillis() / 1000) + "");
273         }
274         if (pMap.get(OAuth.OAUTH_NONCE) == null) {
275             addParameter(OAuth.OAUTH_NONCE, System.nanoTime() + "");
276         }
277         if (pMap.get(OAuth.OAUTH_VERSION) == null) {
278         	addParameter(OAuth.OAUTH_VERSION, OAuth.VERSION_1_0);
279         }
280         this.sign(accessor);
281     }
282 
283     /**
284      * Add a signature to the message.
285      *
286      * @throws URISyntaxException
287      */
sign(OAuthAccessor accessor)288     public void sign(OAuthAccessor accessor) throws IOException,
289             OAuthException, URISyntaxException {
290         OAuthSignatureMethod.newSigner(this, accessor).sign(this);
291     }
292 
293     /**
294      * Check that the message is valid.
295      *
296      * @throws IOException
297      * @throws URISyntaxException
298      *
299      * @throws OAuthProblemException
300      *                 the message is invalid
301      */
validateMessage(OAuthAccessor accessor, OAuthValidator validator)302     public void validateMessage(OAuthAccessor accessor, OAuthValidator validator)
303             throws OAuthException, IOException, URISyntaxException {
304         validator.validateMessage(this, accessor);
305     }
306 
307     /**
308      * Construct a WWW-Authenticate or Authentication header value, containing
309      * the given realm plus all the parameters whose names begin with "oauth_".
310      */
getAuthorizationHeader(String realm)311     public String getAuthorizationHeader(String realm) throws IOException {
312         StringBuilder into = new StringBuilder();
313         if (realm != null) {
314             into.append(" realm=\"").append(OAuth.percentEncode(realm)).append('"');
315         }
316         beforeGetParameter();
317         if (parameters != null) {
318             for (Map.Entry parameter : parameters) {
319                 String name = toString(parameter.getKey());
320                 if (name.startsWith("oauth_")) {
321                     if (into.length() > 0) into.append(",");
322                     into.append(" ");
323                     into.append(OAuth.percentEncode(name)).append("=\"");
324                     into.append(OAuth.percentEncode(toString(parameter.getValue()))).append('"');
325                 }
326             }
327         }
328         return AUTH_SCHEME + into.toString();
329     }
330 
331     /**
332      * Read all the data from the given stream, and close it.
333      *
334      * @return null if from is null, or the data from the stream converted to a
335      *         String
336      */
readAll(InputStream from, String encoding)337     public static String readAll(InputStream from, String encoding) throws IOException
338     {
339         if (from == null) {
340             return null;
341         }
342         try {
343             StringBuilder into = new StringBuilder();
344             Reader r = new InputStreamReader(from, encoding);
345             char[] s = new char[512];
346             for (int n; 0 < (n = r.read(s));) {
347                 into.append(s, 0, n);
348             }
349             return into.toString();
350         } finally {
351             from.close();
352         }
353     }
354 
355     /**
356      * Parse the parameters from an OAuth Authorization or WWW-Authenticate
357      * header. The realm is included as a parameter. If the given header doesn't
358      * start with "OAuth ", return an empty list.
359      */
decodeAuthorization(String authorization)360     public static List<OAuth.Parameter> decodeAuthorization(String authorization) {
361         List<OAuth.Parameter> into = new ArrayList<OAuth.Parameter>();
362         if (authorization != null) {
363             Matcher m = AUTHORIZATION.matcher(authorization);
364             if (m.matches()) {
365                 if (AUTH_SCHEME.equalsIgnoreCase(m.group(1))) {
366                     for (String nvp : m.group(2).split("\\s*,\\s*")) {
367                         m = NVP.matcher(nvp);
368                         if (m.matches()) {
369                             String name = OAuth.decodePercent(m.group(1));
370                             String value = OAuth.decodePercent(m.group(2));
371                             into.add(new OAuth.Parameter(name, value));
372                         }
373                     }
374                 }
375             }
376         }
377         return into;
378     }
379 
380     public static final String AUTH_SCHEME = "OAuth";
381 
382     public static final String GET = "GET";
383     public static final String POST = "POST";
384     public static final String PUT = "PUT";
385     public static final String DELETE = "DELETE";
386 
387     private static final Pattern AUTHORIZATION = Pattern.compile("\\s*(\\w*)\\s+(.*)");
388     private static final Pattern NVP = Pattern.compile("(\\S*)\\s*\\=\\s*\"([^\"]*)\"");
389 
toString(Object from)390     private static final String toString(Object from) {
391         return (from == null) ? null : from.toString();
392     }
393 
394 }
395