summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorNico Sallembien <nsallembien@google.com>2010-01-26 16:54:17 -0800
committerNico Sallembien <nsallembien@google.com>2010-01-26 16:54:17 -0800
commitb852fcf48a8909164d7f323dd02a35d2a8056a61 (patch)
treef48cf3fca303a8d814b61b26470cccc5c9d0e5ed
parentab1c87260e23a28960b7ebc8319d40722a06711a (diff)
downloadoauth-b852fcf48a8909164d7f323dd02a35d2a8056a61.tar.gz
Checking oauth.net external library
-rw-r--r--LICENSE.txt203
-rwxr-xr-xcore/src/main/java/net/oauth/ConsumerProperties.java131
-rwxr-xr-xcore/src/main/java/net/oauth/OAuth.java300
-rwxr-xr-xcore/src/main/java/net/oauth/OAuthAccessor.java92
-rwxr-xr-xcore/src/main/java/net/oauth/OAuthConsumer.java70
-rwxr-xr-xcore/src/main/java/net/oauth/OAuthException.java54
-rwxr-xr-xcore/src/main/java/net/oauth/OAuthMessage.java394
-rwxr-xr-xcore/src/main/java/net/oauth/OAuthProblemException.java106
-rwxr-xr-xcore/src/main/java/net/oauth/OAuthServiceProvider.java42
-rwxr-xr-xcore/src/main/java/net/oauth/OAuthValidator.java43
-rwxr-xr-xcore/src/main/java/net/oauth/SimpleOAuthValidator.java111
-rwxr-xr-xcore/src/main/java/net/oauth/client/ExcerptInputStream.java45
-rwxr-xr-xcore/src/main/java/net/oauth/client/OAuthClient.java348
-rwxr-xr-xcore/src/main/java/net/oauth/client/OAuthResponseMessage.java93
-rwxr-xr-xcore/src/main/java/net/oauth/client/URLConnectionClient.java113
-rwxr-xr-xcore/src/main/java/net/oauth/client/URLConnectionResponse.java136
-rwxr-xr-xcore/src/main/java/net/oauth/client/httpclient4/HttpClient4.java125
-rwxr-xr-xcore/src/main/java/net/oauth/client/httpclient4/HttpClientPool.java36
-rwxr-xr-xcore/src/main/java/net/oauth/client/httpclient4/HttpMethodResponse.java137
-rwxr-xr-xcore/src/main/java/net/oauth/consumer.properties.sample16
-rwxr-xr-xcore/src/main/java/net/oauth/http/HttpClient.java40
-rwxr-xr-xcore/src/main/java/net/oauth/http/HttpMessage.java161
-rwxr-xr-xcore/src/main/java/net/oauth/http/HttpMessageDecoder.java97
-rwxr-xr-xcore/src/main/java/net/oauth/http/HttpResponseMessage.java59
-rwxr-xr-xcore/src/main/java/net/oauth/signature/HMAC_SHA1.java103
-rwxr-xr-xcore/src/main/java/net/oauth/signature/OAuthSignatureMethod.java300
-rwxr-xr-xcore/src/main/java/net/oauth/signature/PLAINTEXT.java65
-rwxr-xr-xcore/src/main/java/net/oauth/signature/RSA_SHA1.java239
28 files changed, 3659 insertions, 0 deletions
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..6b0b127
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,203 @@
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ 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.
+
diff --git a/core/src/main/java/net/oauth/ConsumerProperties.java b/core/src/main/java/net/oauth/ConsumerProperties.java
new file mode 100755
index 0000000..42d22f9
--- /dev/null
+++ b/core/src/main/java/net/oauth/ConsumerProperties.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2007 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.net.MalformedURLException;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * A pool of OAuthConsumers that are constructed from Properties. Each consumer
+ * has a name, which is a property of the OAuthConsumer. Other properties come
+ * from Properties whose names are prefixed with the consumer's name. For
+ * example, a consumer's credentials come from properties named
+ * [name].consumerKey and [name].consumerSecret.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class ConsumerProperties {
+
+ public static URL getResource(String name, ClassLoader loader)
+ throws IOException {
+ URL resource = loader.getResource(name);
+ if (resource == null) {
+ throw new IOException("resource not found: " + name);
+ }
+ return resource;
+ }
+
+ public static Properties getProperties(URL source) throws IOException {
+ InputStream input = source.openStream();
+ try {
+ Properties p = new Properties();
+ p.load(input);
+ return p;
+ } finally {
+ input.close();
+ }
+ }
+
+ public ConsumerProperties(String resourceName, ClassLoader loader)
+ throws IOException {
+ this(getProperties(getResource(resourceName, loader)));
+ }
+
+ public ConsumerProperties(Properties consumerProperties) {
+ this.consumerProperties = consumerProperties;
+ }
+
+ private final Properties consumerProperties;
+
+ private final Map<String, OAuthConsumer> pool = new HashMap<String, OAuthConsumer>();
+
+ /** Get the consumer with the given name. */
+ public OAuthConsumer getConsumer(String name) throws MalformedURLException {
+ OAuthConsumer consumer;
+ synchronized (pool) {
+ consumer = pool.get(name);
+ }
+ if (consumer == null) {
+ consumer = newConsumer(name);
+ }
+ synchronized (pool) {
+ OAuthConsumer first = pool.get(name);
+ if (first == null) {
+ pool.put(name, consumer);
+ } else {
+ /*
+ * Another thread just constructed an identical OAuthConsumer.
+ * Use that one (and discard the one we just constructed).
+ */
+ consumer = first;
+ }
+ }
+ return consumer;
+ }
+
+ protected OAuthConsumer newConsumer(String name)
+ throws MalformedURLException {
+ String base = consumerProperties.getProperty(name
+ + ".serviceProvider.baseURL");
+ URL baseURL = (base == null) ? null : new URL(base);
+ OAuthServiceProvider serviceProvider = new OAuthServiceProvider(getURL(
+ baseURL, name + ".serviceProvider.requestTokenURL"), getURL(
+ baseURL, name + ".serviceProvider.userAuthorizationURL"),
+ getURL(baseURL, name + ".serviceProvider.accessTokenURL"));
+ OAuthConsumer consumer = new OAuthConsumer(consumerProperties
+ .getProperty(name + ".callbackURL"), consumerProperties
+ .getProperty(name + ".consumerKey"), consumerProperties
+ .getProperty(name + ".consumerSecret"), serviceProvider);
+ consumer.setProperty("name", name);
+ if (baseURL != null) {
+ consumer.setProperty("serviceProvider.baseURL", baseURL);
+ }
+ for (Map.Entry prop : consumerProperties.entrySet()) {
+ String propName = (String) prop.getKey();
+ if (propName.startsWith(name + ".consumer.")) {
+ String c = propName.substring(name.length() + 10);
+ consumer.setProperty(c, prop.getValue());
+ }
+ }
+ return consumer;
+ }
+
+ private String getURL(URL base, String name) throws MalformedURLException {
+ String url = consumerProperties.getProperty(name);
+ if (base != null) {
+ url = (new URL(base, url)).toExternalForm();
+ }
+ return url;
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/OAuth.java b/core/src/main/java/net/oauth/OAuth.java
new file mode 100755
index 0000000..aed4c5e
--- /dev/null
+++ b/core/src/main/java/net/oauth/OAuth.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2007 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.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.UnsupportedEncodingException;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author John Kristian
+ * @hide
+ */
+public class OAuth {
+
+ public static final String VERSION_1_0 = "1.0";
+
+ /** The encoding used to represent characters as bytes. */
+ public static final String ENCODING = "UTF-8";
+
+ /** The MIME type for a sequence of OAuth parameters. */
+ public static final String FORM_ENCODED = "application/x-www-form-urlencoded";
+
+ public static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key";
+ public static final String OAUTH_TOKEN = "oauth_token";
+ public static final String OAUTH_TOKEN_SECRET = "oauth_token_secret";
+ public static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method";
+ public static final String OAUTH_SIGNATURE = "oauth_signature";
+ public static final String OAUTH_TIMESTAMP = "oauth_timestamp";
+ public static final String OAUTH_NONCE = "oauth_nonce";
+ public static final String OAUTH_VERSION = "oauth_version";
+
+ public static final String HMAC_SHA1 = "HMAC-SHA1";
+ public static final String RSA_SHA1 = "RSA-SHA1";
+
+ public static class Problems {
+ public static final String TOKEN_NOT_AUTHORIZED = "token_not_authorized";
+ public static final String INVALID_USED_NONCE = "invalid_used_nonce";
+ public static final String SIGNATURE_INVALID = "signature_invalid";
+ public static final String INVALID_EXPIRED_TOKEN = "invalid_expired_token";
+ public static final String INVALID_CONSUMER_KEY = "invalid_consumer_key";
+ public static final String CONSUMER_KEY_REFUSED = "consumer_key_refused";
+ public static final String TIMESTAMP_REFUSED = "timestamp_refused";
+ public static final String PARAMETER_REJECTED = "parameter_rejected";
+ public static final String PARAMETER_ABSENT = "parameter_absent";
+ public static final String VERSION_REJECTED = "version_rejected";
+ public static final String SIGNATURE_METHOD_REJECTED = "signature_method_rejected";
+
+ public static final String OAUTH_PARAMETERS_ABSENT = "oauth_parameters_absent";
+ public static final String OAUTH_PARAMETERS_REJECTED = "oauth_parameters_rejected";
+ public static final String OAUTH_ACCEPTABLE_TIMESTAMPS = "oauth_acceptable_timestamps";
+ public static final String OAUTH_ACCEPTABLE_VERSIONS = "oauth_acceptable_versions";
+ }
+
+ /** Return true if the given Content-Type header means FORM_ENCODED. */
+ public static boolean isFormEncoded(String contentType) {
+ if (contentType == null) {
+ return false;
+ }
+ int semi = contentType.indexOf(";");
+ if (semi >= 0) {
+ contentType = contentType.substring(0, semi);
+ }
+ return FORM_ENCODED.equalsIgnoreCase(contentType.trim());
+ }
+
+ /**
+ * Construct a form-urlencoded document containing the given sequence of
+ * name/value pairs. Use OAuth percent encoding (not exactly the encoding
+ * mandated by HTTP).
+ */
+ public static String formEncode(Iterable<? extends Map.Entry> parameters)
+ throws IOException {
+ ByteArrayOutputStream b = new ByteArrayOutputStream();
+ formEncode(parameters, b);
+ return new String(b.toByteArray());
+ }
+
+ /**
+ * Write a form-urlencoded document into the given stream, containing the
+ * given sequence of name/value pairs.
+ */
+ public static void formEncode(Iterable<? extends Map.Entry> parameters,
+ OutputStream into) throws IOException {
+ if (parameters != null) {
+ boolean first = true;
+ for (Map.Entry parameter : parameters) {
+ if (first) {
+ first = false;
+ } else {
+ into.write('&');
+ }
+ into.write(percentEncode(toString(parameter.getKey()))
+ .getBytes());
+ into.write('=');
+ into.write(percentEncode(toString(parameter.getValue()))
+ .getBytes());
+ }
+ }
+ }
+
+ /** Parse a form-urlencoded document. */
+ public static List<Parameter> decodeForm(String form) {
+ List<Parameter> list = new ArrayList<Parameter>();
+ if (!isEmpty(form)) {
+ for (String nvp : form.split("\\&")) {
+ int equals = nvp.indexOf('=');
+ String name;
+ String value;
+ if (equals < 0) {
+ name = decodePercent(nvp);
+ value = null;
+ } else {
+ name = decodePercent(nvp.substring(0, equals));
+ value = decodePercent(nvp.substring(equals + 1));
+ }
+ list.add(new Parameter(name, value));
+ }
+ }
+ return list;
+ }
+
+ /** Construct a &-separated list of the given values, percentEncoded. */
+ public static String percentEncode(Iterable values) {
+ StringBuilder p = new StringBuilder();
+ for (Object v : values) {
+ if (p.length() > 0) {
+ p.append("&");
+ }
+ p.append(OAuth.percentEncode(toString(v)));
+ }
+ return p.toString();
+ }
+
+ public static String percentEncode(String s) {
+ if (s == null) {
+ return "";
+ }
+ try {
+ return URLEncoder.encode(s, ENCODING)
+ // OAuth encodes some characters differently:
+ .replace("+", "%20").replace("*", "%2A")
+ .replace("%7E", "~");
+ // This could be done faster with more hand-crafted code.
+ } catch (UnsupportedEncodingException wow) {
+ throw new RuntimeException(wow.getMessage(), wow);
+ }
+ }
+
+ public static String decodePercent(String s) {
+ try {
+ return URLDecoder.decode(s, ENCODING);
+ // This implements http://oauth.pbwiki.com/FlexibleDecoding
+ } catch (java.io.UnsupportedEncodingException wow) {
+ throw new RuntimeException(wow.getMessage(), wow);
+ }
+ }
+
+ /**
+ * Construct a Map containing a copy of the given parameters. If several
+ * parameters have the same name, the Map will contain the first value,
+ * only.
+ */
+ public static Map<String, String> newMap(Iterable<? extends Map.Entry> from) {
+ Map<String, String> map = new HashMap<String, String>();
+ if (from != null) {
+ for (Map.Entry f : from) {
+ String key = toString(f.getKey());
+ if (!map.containsKey(key)) {
+ map.put(key, toString(f.getValue()));
+ }
+ }
+ }
+ return map;
+ }
+
+ /** Construct a list of Parameters from name, value, name, value... */
+ public static List<Parameter> newList(String... parameters) {
+ List<Parameter> list = new ArrayList<Parameter>(parameters.length / 2);
+ for (int p = 0; p + 1 < parameters.length; p += 2) {
+ list.add(new Parameter(parameters[p], parameters[p + 1]));
+ }
+ return list;
+ }
+
+ /** A name/value pair. */
+ public static class Parameter implements Map.Entry<String, String> {
+
+ public Parameter(String key, String value) {
+ this.key = key;
+ this.value = value;
+ }
+
+ private final String key;
+
+ private String value;
+
+ public String getKey() {
+ return key;
+ }
+
+ public String getValue() {
+ return value;
+ }
+
+ public String setValue(String value) {
+ try {
+ return this.value;
+ } finally {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public String toString() {
+ return percentEncode(getKey()) + '=' + percentEncode(getValue());
+ }
+
+ @Override
+ public int hashCode()
+ {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + ((key == null) ? 0 : key.hashCode());
+ result = prime * result + ((value == null) ? 0 : value.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (this == obj)
+ return true;
+ if (obj == null)
+ return false;
+ if (getClass() != obj.getClass())
+ return false;
+ final Parameter that = (Parameter) obj;
+ if (key == null) {
+ if (that.key != null)
+ return false;
+ } else if (!key.equals(that.key))
+ return false;
+ if (value == null) {
+ if (that.value != null)
+ return false;
+ } else if (!value.equals(that.value))
+ return false;
+ return true;
+ }
+ }
+
+ private static final String toString(Object from) {
+ return (from == null) ? null : from.toString();
+ }
+
+ /**
+ * Construct a URL like the given one, but with the given parameters added
+ * to its query string.
+ */
+ public static String addParameters(String url, String... parameters)
+ throws IOException {
+ return addParameters(url, newList(parameters));
+ }
+
+ public static String addParameters(String url,
+ Iterable<? extends Map.Entry<String, String>> parameters)
+ throws IOException {
+ String form = formEncode(parameters);
+ if (form == null || form.length() <= 0) {
+ return url;
+ } else {
+ return url + ((url.indexOf("?") < 0) ? '?' : '&') + form;
+ }
+ }
+
+ public static boolean isEmpty(String str) {
+ return (str == null) || (str.length() == 0);
+ }
+}
diff --git a/core/src/main/java/net/oauth/OAuthAccessor.java b/core/src/main/java/net/oauth/OAuthAccessor.java
new file mode 100755
index 0000000..043d6d6
--- /dev/null
+++ b/core/src/main/java/net/oauth/OAuthAccessor.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2007 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.Serializable;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import net.oauth.http.HttpMessage;
+
+/**
+ * Properties of one User of an OAuthConsumer. Properties may be added freely,
+ * e.g. to support extensions.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class OAuthAccessor implements Serializable {
+
+ private static final long serialVersionUID = 5590788443138352999L;
+
+ public final OAuthConsumer consumer;
+ public String requestToken;
+ public String accessToken;
+ public String tokenSecret;
+
+ public OAuthAccessor(OAuthConsumer consumer) {
+ this.consumer = consumer;
+ this.requestToken = null;
+ this.accessToken = null;
+ this.tokenSecret = null;
+ }
+
+ private final Map<String, Object> properties = new HashMap<String, Object>();
+
+ public Object getProperty(String name) {
+ return properties.get(name);
+ }
+
+ public void setProperty(String name, Object value) {
+ properties.put(name, value);
+ }
+
+ /**
+ * Construct a request message containing the given parameters but no body.
+ * Don't send the message, merely construct it. The caller will ordinarily
+ * send it, for example by calling OAuthClient.invoke.
+ *
+ * @param method
+ * the HTTP request method. If this is null, use the default
+ * method; that is getProperty("httpMethod") or (if that's null)
+ * consumer.getProperty("httpMethod") or (if that's null)
+ * OAuthMessage.GET.
+ */
+ public OAuthMessage newRequestMessage(String method, String url,
+ Collection<? extends Map.Entry> parameters)
+ throws OAuthException, IOException, URISyntaxException {
+ if (method == null) {
+ method = (String) this.getProperty("httpMethod");
+ if (method == null) {
+ method = (String) this.consumer.getProperty("httpMethod");
+ if (method == null) {
+ method = OAuthMessage.GET;
+ }
+ }
+ }
+ OAuthMessage message = new OAuthMessage(method, url, parameters);
+ message.addRequiredParameters(this);
+ Object accepted = consumer.getProperty(OAuthConsumer.ACCEPT_ENCODING);
+ if (accepted != null) {
+ message.getHeaders().add(new OAuth.Parameter(HttpMessage.ACCEPT_ENCODING, accepted.toString()));
+ }
+ return message;
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/OAuthConsumer.java b/core/src/main/java/net/oauth/OAuthConsumer.java
new file mode 100755
index 0000000..31cc9ff
--- /dev/null
+++ b/core/src/main/java/net/oauth/OAuthConsumer.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2007 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.Serializable;
+import java.util.HashMap;
+import java.util.Map;
+import net.oauth.http.HttpMessage;
+
+/**
+ * Properties of an OAuth Consumer. Properties may be added freely, e.g. to
+ * support extensions.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class OAuthConsumer implements Serializable {
+
+ private static final long serialVersionUID = -2258581186977818580L;
+
+ public final String callbackURL;
+ public final String consumerKey;
+ public final String consumerSecret;
+ public final OAuthServiceProvider serviceProvider;
+
+ public OAuthConsumer(String callbackURL, String consumerKey,
+ String consumerSecret, OAuthServiceProvider serviceProvider) {
+ this.callbackURL = callbackURL;
+ this.consumerKey = consumerKey;
+ this.consumerSecret = consumerSecret;
+ this.serviceProvider = serviceProvider;
+ }
+
+ private final Map<String, Object> properties = new HashMap<String, Object>();
+
+ public Object getProperty(String name) {
+ return properties.get(name);
+ }
+
+ public void setProperty(String name, Object value) {
+ properties.put(name, value);
+ }
+
+ /**
+ * The name of the property whose value is the Accept-Encoding header in
+ * HTTP requests.
+ */
+ public static final String ACCEPT_ENCODING = "HTTP.header." + HttpMessage.ACCEPT_ENCODING;
+
+ /**
+ * The name of the property whose value is the <a
+ * href="http://oauth.pbwiki.com/AccessorSecret">Accessor Secret</a>.
+ */
+ public static final String ACCESSOR_SECRET = "oauth_accessor_secret";
+
+}
diff --git a/core/src/main/java/net/oauth/OAuthException.java b/core/src/main/java/net/oauth/OAuthException.java
new file mode 100755
index 0000000..94f6451
--- /dev/null
+++ b/core/src/main/java/net/oauth/OAuthException.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2008 Google, 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;
+
+/**
+ * Superclass for extensions thrown by the OAuth library.
+ * @hide
+ */
+public class OAuthException extends Exception {
+
+ /**
+ * For subclasses only.
+ */
+ protected OAuthException() {
+ }
+
+ /**
+ * @param message
+ */
+ public OAuthException(String message) {
+ super(message);
+ }
+
+ /**
+ * @param cause
+ */
+ public OAuthException(Throwable cause) {
+ super(cause);
+ }
+
+ /**
+ * @param message
+ * @param cause
+ */
+ public OAuthException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/core/src/main/java/net/oauth/OAuthMessage.java b/core/src/main/java/net/oauth/OAuthMessage.java
new file mode 100755
index 0000000..e16ca9b
--- /dev/null
+++ b/core/src/main/java/net/oauth/OAuthMessage.java
@@ -0,0 +1,394 @@
+/*
+ * 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();
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/OAuthProblemException.java b/core/src/main/java/net/oauth/OAuthProblemException.java
new file mode 100755
index 0000000..7ef0f41
--- /dev/null
+++ b/core/src/main/java/net/oauth/OAuthProblemException.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2007 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.util.HashMap;
+import java.util.Map;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+
+/**
+ * Describes an OAuth-related problem, using a set of named parameters. One
+ * parameter identifies the basic problem, and the others provide supplementary
+ * diagnostic information. This can be used to capture information from a
+ * response that conforms to the OAuth <a
+ * href="http://wiki.oauth.net/ProblemReporting">Problem Reporting
+ * extension</a>.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class OAuthProblemException extends OAuthException {
+
+ public static final String OAUTH_PROBLEM = "oauth_problem";
+
+ public OAuthProblemException() {
+ }
+
+ public OAuthProblemException(String problem) {
+ super(problem);
+ if (problem != null) {
+ parameters.put(OAUTH_PROBLEM, problem);
+ }
+ }
+
+ private final Map<String, Object> parameters = new HashMap<String, Object>();
+
+ @Override
+ public String getMessage() {
+ String msg = super.getMessage();
+ if (msg != null)
+ return msg;
+ msg = getProblem();
+ if (msg != null)
+ return msg;
+ Object response = getParameters().get(HttpMessage.RESPONSE);
+ if (response != null) {
+ msg = response.toString();
+ int eol = msg.indexOf("\n");
+ if (eol < 0) {
+ eol = msg.indexOf("\r");
+ }
+ if (eol >= 0) {
+ msg = msg.substring(0, eol);
+ }
+ msg = msg.trim();
+ if (msg.length() > 0) {
+ return msg;
+ }
+ }
+ response = getHttpStatusCode();
+ if (response != null) {
+ return HttpResponseMessage.STATUS_CODE + " " + response;
+ }
+ return null;
+ }
+
+ public void setParameter(String name, Object value) {
+ getParameters().put(name, value);
+ }
+
+ public Map<String, Object> getParameters() {
+ return parameters;
+ }
+
+ public String getProblem() {
+ return (String) getParameters().get(OAUTH_PROBLEM);
+ }
+
+ public int getHttpStatusCode() {
+ Object code = getParameters().get(HttpResponseMessage.STATUS_CODE);
+ if (code == null) {
+ return 200;
+ } else if (code instanceof Number) { // the usual case
+ return ((Number) code).intValue();
+ } else {
+ return Integer.parseInt(code.toString());
+ }
+ }
+
+ private static final long serialVersionUID = 1L;
+
+}
diff --git a/core/src/main/java/net/oauth/OAuthServiceProvider.java b/core/src/main/java/net/oauth/OAuthServiceProvider.java
new file mode 100755
index 0000000..2061449
--- /dev/null
+++ b/core/src/main/java/net/oauth/OAuthServiceProvider.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2007 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.Serializable;
+
+/**
+ * Properties of an OAuth Service Provider.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class OAuthServiceProvider implements Serializable {
+
+ private static final long serialVersionUID = 3306534392621038574L;
+
+ public final String requestTokenURL;
+ public final String userAuthorizationURL;
+ public final String accessTokenURL;
+
+ public OAuthServiceProvider(String requestTokenURL,
+ String userAuthorizationURL, String accessTokenURL) {
+ this.requestTokenURL = requestTokenURL;
+ this.userAuthorizationURL = userAuthorizationURL;
+ this.accessTokenURL = accessTokenURL;
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/OAuthValidator.java b/core/src/main/java/net/oauth/OAuthValidator.java
new file mode 100755
index 0000000..8d272a8
--- /dev/null
+++ b/core/src/main/java/net/oauth/OAuthValidator.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2008 Google, 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.net.URISyntaxException;
+
+/**
+ * An algorithm to determine whether a message has a valid signature, a correct
+ * version number, a fresh timestamp, etc.
+ *
+ * @author Dirk Balfanz
+ * @author John Kristian
+ * @hide
+ */
+public interface OAuthValidator {
+
+ /**
+ * Check that the given message from the given accessor is valid.
+ * @throws OAuthException TODO
+ * @throws IOException TODO
+ * @throws URISyntaxException
+ * @throws OAuthProblemException the message is invalid.
+ * The implementation should throw exceptions that conform to the OAuth
+ * <a href="http://wiki.oauth.net/ProblemReporting">Problem Reporting extension</a>.
+ */
+ public void validateMessage(OAuthMessage message, OAuthAccessor accessor)
+ throws OAuthException, IOException, URISyntaxException;
+
+}
diff --git a/core/src/main/java/net/oauth/SimpleOAuthValidator.java b/core/src/main/java/net/oauth/SimpleOAuthValidator.java
new file mode 100755
index 0000000..80a7d42
--- /dev/null
+++ b/core/src/main/java/net/oauth/SimpleOAuthValidator.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2008 Google, 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.net.URISyntaxException;
+
+import net.oauth.signature.OAuthSignatureMethod;
+
+/**
+ * A simple OAuthValidator, which checks the version, whether the timestamp
+ * is close to now and the signature is valid. Each check may be overridden.
+ *
+ * @author Dirk Balfanz
+ * @author John Kristian
+ * @hide
+ */
+public class SimpleOAuthValidator implements OAuthValidator {
+
+ // default window for timestamps is 5 minutes
+ public static final long DEFAULT_TIMESTAMP_WINDOW = 5 * 60 * 1000L;
+
+ /**
+ * Construct a validator that rejects messages more than five minutes out
+ * of date, or with a OAuth version other than 1.0, or with an invalid
+ * signature.
+ */
+ public SimpleOAuthValidator() {
+ this(DEFAULT_TIMESTAMP_WINDOW, Double.parseDouble(OAuth.VERSION_1_0));
+ }
+
+ /**
+ * Public constructor.
+ *
+ * @param timestampWindowSec
+ * specifies, in seconds, the windows (into the past and
+ * into the future) in which we'll accept timestamps.
+ * @param maxVersion
+ * the maximum acceptable oauth_version
+ */
+ public SimpleOAuthValidator(long timestampWindowMsec, double maxVersion) {
+ this.timestampWindow = timestampWindowMsec;
+ this.maxVersion = maxVersion;
+ }
+
+ protected final double minVersion = 1.0;
+ protected final double maxVersion;
+ protected final long timestampWindow;
+
+ /** {@inherit}
+ * @throws URISyntaxException */
+ public void validateMessage(OAuthMessage message, OAuthAccessor accessor)
+ throws OAuthException, IOException, URISyntaxException {
+ validateVersion(message);
+ validateTimestampAndNonce(message);
+ validateSignature(message, accessor);
+ }
+
+ protected void validateVersion(OAuthMessage message)
+ throws OAuthException, IOException {
+ String versionString = message.getParameter(OAuth.OAUTH_VERSION);
+ if (versionString != null) {
+ double version = Double.parseDouble(versionString);
+ if (version < minVersion || maxVersion < version) {
+ OAuthProblemException problem = new OAuthProblemException("version_rejected");
+ problem.setParameter("oauth_acceptable_versions", minVersion + "-" + maxVersion);
+ throw problem;
+ }
+ }
+ }
+
+ /** This implementation doesn't check the nonce value. */
+ protected void validateTimestampAndNonce(OAuthMessage message)
+ throws IOException, OAuthProblemException {
+ message.requireParameters(OAuth.OAUTH_TIMESTAMP, OAuth.OAUTH_NONCE);
+ long timestamp = Long.parseLong(message.getParameter(OAuth.OAUTH_TIMESTAMP)) * 1000L;
+ long now = currentTimeMsec();
+ long min = now - timestampWindow;
+ long max = now + timestampWindow;
+ if (timestamp < min || max < timestamp) {
+ OAuthProblemException problem = new OAuthProblemException("timestamp_refused");
+ problem.setParameter("oauth_acceptable_timestamps", min + "-" + max);
+ throw problem;
+ }
+ }
+
+ protected void validateSignature(OAuthMessage message, OAuthAccessor accessor)
+ throws OAuthException, IOException, URISyntaxException {
+ message.requireParameters(OAuth.OAUTH_CONSUMER_KEY,
+ OAuth.OAUTH_SIGNATURE_METHOD, OAuth.OAUTH_SIGNATURE);
+ OAuthSignatureMethod.newSigner(message, accessor).validate(message);
+ }
+
+ protected long currentTimeMsec() {
+ return System.currentTimeMillis();
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/client/ExcerptInputStream.java b/core/src/main/java/net/oauth/client/ExcerptInputStream.java
new file mode 100755
index 0000000..0dae3c3
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/ExcerptInputStream.java
@@ -0,0 +1,45 @@
+package net.oauth.client;
+
+import java.io.BufferedInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A decorator that retains a copy of the first few bytes of data.
+ * @hide
+ */
+public class ExcerptInputStream extends BufferedInputStream
+{
+ /**
+ * A marker that's appended to the excerpt if it's less than the complete
+ * stream.
+ */
+ public static final byte[] ELLIPSIS = " ...".getBytes();
+
+ public ExcerptInputStream(InputStream in) throws IOException {
+ super(in);
+ mark(LIMIT);
+ int total = 0;
+ int read;
+ while ((read = read(excerpt, total, LIMIT - total)) != -1 && ((total += read) < LIMIT));
+ if (total == LIMIT) {
+ // Only add the ellipsis if there are at least LIMIT bytes
+ System.arraycopy(ELLIPSIS, 0, excerpt, total, ELLIPSIS.length);
+ } else {
+ byte[] tmp = new byte[total];
+ System.arraycopy(excerpt, 0, tmp, 0, total);
+ excerpt = tmp;
+ }
+ reset();
+ }
+
+ private static final int LIMIT = 1024;
+ private byte[] excerpt = new byte[LIMIT + ELLIPSIS.length];
+
+ /** The first few bytes of data, plus ELLIPSIS if there are more bytes. */
+ public byte[] getExcerpt()
+ {
+ return excerpt;
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/client/OAuthClient.java b/core/src/main/java/net/oauth/client/OAuthClient.java
new file mode 100755
index 0000000..4be9e7f
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/OAuthClient.java
@@ -0,0 +1,348 @@
+/*
+ * 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.client;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import net.oauth.OAuth;
+import net.oauth.OAuthAccessor;
+import net.oauth.OAuthConsumer;
+import net.oauth.OAuthException;
+import net.oauth.OAuthMessage;
+import net.oauth.OAuthProblemException;
+import net.oauth.http.HttpClient;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpMessageDecoder;
+import net.oauth.http.HttpResponseMessage;
+
+/**
+ * Methods for an OAuth consumer to request tokens from a service provider.
+ * <p>
+ * This class can also be used to request access to protected resources, in some
+ * cases. But not in all cases. For example, this class can't handle arbitrary
+ * HTTP headers.
+ * <p>
+ * Methods of this class return a response as an OAuthMessage, from which you
+ * can get a body or parameters but not both. Calling a getParameter method will
+ * read and close the body (like readBodyAsString), so you can't read it later.
+ * If you read or close the body first, then getParameter can't read it. The
+ * response headers should tell you whether the response contains encoded
+ * parameters, that is whether you should call getParameter or not.
+ * <p>
+ * Methods of this class don't follow redirects. When they receive a redirect
+ * response, they throw an OAuthProblemException, with properties
+ * HttpResponseMessage.STATUS_CODE = the redirect code
+ * HttpResponseMessage.LOCATION = the redirect URL. Such a redirect can't be
+ * handled at the HTTP level, if the second request must carry another OAuth
+ * signature (with different parameters). For example, Google's Service Provider
+ * routinely redirects requests for access to protected resources, and requires
+ * the redirected request to be signed.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class OAuthClient {
+
+ public OAuthClient(HttpClient http)
+ {
+ this.http = http;
+ }
+
+ private HttpClient http;
+
+ public void setHttpClient(HttpClient http) {
+ this.http = http;
+ }
+
+ public HttpClient getHttpClient() {
+ return http;
+ }
+
+ /**
+ * Get a fresh request token from the service provider.
+ *
+ * @param accessor
+ * should contain a consumer that contains a non-null consumerKey
+ * and consumerSecret. Also,
+ * accessor.consumer.serviceProvider.requestTokenURL should be
+ * the URL (determined by the service provider) for getting a
+ * request token.
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public void getRequestToken(OAuthAccessor accessor) throws IOException,
+ OAuthException, URISyntaxException {
+ getRequestToken(accessor, null);
+ }
+
+ /**
+ * Get a fresh request token from the service provider.
+ *
+ * @param accessor
+ * should contain a consumer that contains a non-null consumerKey
+ * and consumerSecret. Also,
+ * accessor.consumer.serviceProvider.requestTokenURL should be
+ * the URL (determined by the service provider) for getting a
+ * request token.
+ * @param httpMethod
+ * typically OAuthMessage.POST or OAuthMessage.GET, or null to
+ * use the default method.
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public void getRequestToken(OAuthAccessor accessor, String httpMethod)
+ throws IOException, OAuthException, URISyntaxException {
+ getRequestToken(accessor, httpMethod, null);
+ }
+
+ /** Get a fresh request token from the service provider.
+ *
+ * @param accessor
+ * should contain a consumer that contains a non-null consumerKey
+ * and consumerSecret. Also,
+ * accessor.consumer.serviceProvider.requestTokenURL should be
+ * the URL (determined by the service provider) for getting a
+ * request token.
+ * @param httpMethod
+ * typically OAuthMessage.POST or OAuthMessage.GET, or null to
+ * use the default method.
+ * @param parameters
+ * additional parameters for this request, or null to indicate
+ * that there are no additional parameters.
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public void getRequestToken(OAuthAccessor accessor, String httpMethod,
+ Collection<? extends Map.Entry> parameters) throws IOException,
+ OAuthException, URISyntaxException {
+ accessor.accessToken = null;
+ accessor.tokenSecret = null;
+ {
+ // This code supports the 'Variable Accessor Secret' extension
+ // described in http://oauth.pbwiki.com/AccessorSecret
+ Object accessorSecret = accessor
+ .getProperty(OAuthConsumer.ACCESSOR_SECRET);
+ if (accessorSecret != null) {
+ List<Map.Entry> p = (parameters == null) ? new ArrayList<Map.Entry>(
+ 1)
+ : new ArrayList<Map.Entry>(parameters);
+ p.add(new OAuth.Parameter("oauth_accessor_secret",
+ accessorSecret.toString()));
+ parameters = p;
+ // But don't modify the caller's parameters.
+ }
+ }
+ OAuthMessage response = invoke(accessor, httpMethod,
+ accessor.consumer.serviceProvider.requestTokenURL, parameters);
+ accessor.requestToken = response.getParameter(OAuth.OAUTH_TOKEN);
+ accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET);
+ response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET);
+ }
+
+ /**
+ * Get an access token from the service provider, in exchange for an
+ * authorized request token.
+ *
+ * @param accessor
+ * should contain a non-null requestToken and tokenSecret, and a
+ * consumer that contains a consumerKey and consumerSecret. Also,
+ * accessor.consumer.serviceProvider.accessTokenURL should be the
+ * URL (determined by the service provider) for getting an access
+ * token.
+ * @param httpMethod
+ * typically OAuthMessage.POST or OAuthMessage.GET, or null to
+ * use the default method.
+ * @param parameters
+ * additional parameters for this request, or null to indicate
+ * that there are no additional parameters.
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public OAuthMessage getAccessToken(OAuthAccessor accessor, String httpMethod,
+ Collection<? extends Map.Entry> parameters) throws IOException, OAuthException, URISyntaxException {
+ if (accessor.requestToken != null) {
+ if (parameters == null) {
+ parameters = OAuth.newList(OAuth.OAUTH_TOKEN, accessor.requestToken);
+ } else if (!OAuth.newMap(parameters).containsKey(OAuth.OAUTH_TOKEN)) {
+ List<Map.Entry> p = new ArrayList<Map.Entry>(parameters);
+ p.add(new OAuth.Parameter(OAuth.OAUTH_TOKEN, accessor.requestToken));
+ parameters = p;
+ }
+ }
+ OAuthMessage response = invoke(accessor, httpMethod,
+ accessor.consumer.serviceProvider.accessTokenURL, parameters);
+ response.requireParameters(OAuth.OAUTH_TOKEN, OAuth.OAUTH_TOKEN_SECRET);
+ accessor.accessToken = response.getParameter(OAuth.OAUTH_TOKEN);
+ accessor.tokenSecret = response.getParameter(OAuth.OAUTH_TOKEN_SECRET);
+ return response;
+ }
+
+ /**
+ * Construct a request message, send it to the service provider and get the
+ * response.
+ *
+ * @param httpMethod
+ * the HTTP request method, or null to use the default method
+ * @return the response
+ * @throws URISyntaxException
+ * the given url isn't valid syntactically
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public OAuthMessage invoke(OAuthAccessor accessor, String httpMethod,
+ String url, Collection<? extends Map.Entry> parameters)
+ throws IOException, OAuthException, URISyntaxException {
+ String ps = (String) accessor.consumer.getProperty(PARAMETER_STYLE);
+ ParameterStyle style = (ps == null) ? ParameterStyle.BODY : Enum
+ .valueOf(ParameterStyle.class, ps);
+ OAuthMessage request = accessor.newRequestMessage(httpMethod, url,
+ parameters);
+ return invoke(request, style);
+ }
+
+ /**
+ * The name of the OAuthConsumer property whose value is the ParameterStyle
+ * to be used by invoke.
+ */
+ public static final String PARAMETER_STYLE = "parameterStyle";
+
+ /**
+ * The name of the OAuthConsumer property whose value is the Accept-Encoding
+ * header in HTTP requests.
+ * @deprecated use {@link OAuthConsumer#ACCEPT_ENCODING} instead
+ */
+ @Deprecated
+ public static final String ACCEPT_ENCODING = OAuthConsumer.ACCEPT_ENCODING;
+
+ /**
+ * Construct a request message, send it to the service provider and get the
+ * response.
+ *
+ * @return the response
+ * @throws URISyntaxException
+ * the given url isn't valid syntactically
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public OAuthMessage invoke(OAuthAccessor accessor, String url,
+ Collection<? extends Map.Entry> parameters) throws IOException,
+ OAuthException, URISyntaxException {
+ return invoke(accessor, null, url, parameters);
+ }
+
+ /**
+ * Send a request message to the service provider and get the response.
+ *
+ * @return the response
+ * @throws IOException
+ * failed to communicate with the service provider
+ * @throws OAuthProblemException
+ * the HTTP response status code was not 200 (OK)
+ */
+ public OAuthMessage invoke(OAuthMessage request, ParameterStyle style)
+ throws IOException, OAuthException {
+ final boolean isPost = POST.equalsIgnoreCase(request.method);
+ InputStream body = request.getBodyAsStream();
+ if (style == ParameterStyle.BODY && !(isPost && body == null)) {
+ style = ParameterStyle.QUERY_STRING;
+ }
+ String url = request.URL;
+ final List<Map.Entry<String, String>> headers =
+ new ArrayList<Map.Entry<String, String>>(request.getHeaders());
+ switch (style) {
+ case QUERY_STRING:
+ url = OAuth.addParameters(url, request.getParameters());
+ break;
+ case BODY: {
+ byte[] form = OAuth.formEncode(request.getParameters()).getBytes(
+ request.getBodyEncoding());
+ headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE,
+ OAuth.FORM_ENCODED));
+ headers.add(new OAuth.Parameter(CONTENT_LENGTH, form.length + ""));
+ body = new ByteArrayInputStream(form);
+ break;
+ }
+ case AUTHORIZATION_HEADER:
+ headers.add(new OAuth.Parameter("Authorization", request.getAuthorizationHeader(null)));
+ // Find the non-OAuth parameters:
+ List<Map.Entry<String, String>> others = request.getParameters();
+ if (others != null && !others.isEmpty()) {
+ others = new ArrayList<Map.Entry<String, String>>(others);
+ for (Iterator<Map.Entry<String, String>> p = others.iterator(); p
+ .hasNext();) {
+ if (p.next().getKey().startsWith("oauth_")) {
+ p.remove();
+ }
+ }
+ // Place the non-OAuth parameters elsewhere in the request:
+ if (isPost && body == null) {
+ byte[] form = OAuth.formEncode(others).getBytes(
+ request.getBodyEncoding());
+ headers.add(new OAuth.Parameter(HttpMessage.CONTENT_TYPE,
+ OAuth.FORM_ENCODED));
+ headers.add(new OAuth.Parameter(CONTENT_LENGTH, form.length
+ + ""));
+ body = new ByteArrayInputStream(form);
+ } else {
+ url = OAuth.addParameters(url, others);
+ }
+ }
+ break;
+ }
+ final HttpMessage httpRequest = new HttpMessage(request.method, new URL(url), body);
+ httpRequest.headers.addAll(headers);
+ HttpResponseMessage httpResponse = http.execute(httpRequest);
+ httpResponse = HttpMessageDecoder.decode(httpResponse);
+ OAuthMessage response = new OAuthResponseMessage(httpResponse);
+ if (httpResponse.getStatusCode() != HttpResponseMessage.STATUS_OK) {
+ OAuthProblemException problem = new OAuthProblemException();
+ try {
+ response.getParameters(); // decode the response body
+ } catch (IOException ignored) {
+ }
+ problem.getParameters().putAll(response.getDump());
+ try {
+ InputStream b = response.getBodyAsStream();
+ if (b != null) {
+ b.close(); // release resources
+ }
+ } catch (IOException ignored) {
+ }
+ throw problem;
+ }
+ return response;
+ }
+
+ /** Where to place parameters in an HTTP message. */
+ public enum ParameterStyle {
+ AUTHORIZATION_HEADER, BODY, QUERY_STRING;
+ };
+
+ protected static final String PUT = OAuthMessage.PUT;
+ protected static final String POST = OAuthMessage.POST;
+ protected static final String DELETE = OAuthMessage.DELETE;
+ protected static final String CONTENT_LENGTH = HttpMessage.CONTENT_LENGTH;
+
+}
diff --git a/core/src/main/java/net/oauth/client/OAuthResponseMessage.java b/core/src/main/java/net/oauth/client/OAuthResponseMessage.java
new file mode 100755
index 0000000..88a1417
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/OAuthResponseMessage.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 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.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import net.oauth.OAuth;
+import net.oauth.OAuthMessage;
+import net.oauth.OAuthProblemException;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+
+/**
+ * An HTTP response, encapsulated as an OAuthMessage.
+ *
+ * @author John Kristian
+ * @hide
+ */
+final class OAuthResponseMessage extends OAuthMessage
+{
+ OAuthResponseMessage(HttpResponseMessage http) throws IOException
+ {
+ super(http.method, http.url.toExternalForm(), null);
+ this.http = http;
+ getHeaders().addAll(http.headers);
+ for (Map.Entry<String, String> header : http.headers) {
+ if ("WWW-Authenticate".equalsIgnoreCase(header.getKey())) {
+ for (OAuth.Parameter parameter : decodeAuthorization(header.getValue())) {
+ if (!"realm".equalsIgnoreCase(parameter.getKey())) {
+ addParameter(parameter);
+ }
+ }
+ }
+ }
+ }
+
+ private final HttpMessage http;
+
+ @Override
+ public InputStream getBodyAsStream() throws IOException
+ {
+ return http.getBody();
+ }
+
+ @Override
+ public String getBodyEncoding()
+ {
+ return http.getContentCharset();
+ }
+
+ @Override
+ protected void completeParameters() throws IOException
+ {
+ super.completeParameters();
+ String body = readBodyAsString();
+ if (body != null) {
+ addParameters(OAuth.decodeForm(body.trim()));
+ }
+ }
+
+ @Override
+ protected void dump(Map<String, Object> into) throws IOException
+ {
+ super.dump(into);
+ http.dump(into);
+ }
+
+ @Override
+ public void requireParameters(String... names) throws OAuthProblemException, IOException {
+ try {
+ super.requireParameters(names);
+ } catch (OAuthProblemException problem) {
+ problem.getParameters().putAll(getDump());
+ throw problem;
+ }
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/client/URLConnectionClient.java b/core/src/main/java/net/oauth/client/URLConnectionClient.java
new file mode 100755
index 0000000..18bdf47
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/URLConnectionClient.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 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.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import net.oauth.http.HttpClient;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+
+/**
+ * An HttpClient based on HttpURLConnection.
+ * <p>
+ * HttpClient3 or HttpClient4 perform better than this class, as a rule; since
+ * they do things like connection pooling. They also support reading the body
+ * of an HTTP response whose status code isn't 200 (OK), which can enable your
+ * application to handle problems better.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class URLConnectionClient implements HttpClient {
+
+ /** Send a message to the service provider and get the response. */
+ public HttpResponseMessage execute(HttpMessage request) throws IOException {
+ final String httpMethod = request.method;
+ final Collection<Map.Entry<String, String>> addHeaders = request.headers;
+ final URL url = request.url;
+ final URLConnection connection = url.openConnection();
+ connection.setDoInput(true);
+ if (connection instanceof HttpURLConnection) {
+ HttpURLConnection http = (HttpURLConnection) connection;
+ http.setRequestMethod(httpMethod);
+ http.setInstanceFollowRedirects(false);
+ }
+ StringBuilder headers = new StringBuilder(httpMethod);
+ {
+ headers.append(" ").append(url.getPath());
+ String query = url.getQuery();
+ if (query != null && query.length() > 0) {
+ headers.append("?").append(query);
+ }
+ headers.append(EOL);
+ for (Map.Entry<String, List<String>> header : connection
+ .getRequestProperties().entrySet()) {
+ String key = header.getKey();
+ for (String value : header.getValue()) {
+ headers.append(key).append(": ").append(value).append(EOL);
+ }
+ }
+ }
+ String contentLength = null;
+ for (Map.Entry<String, String> header : addHeaders) {
+ String key = header.getKey();
+ if (HttpMessage.CONTENT_LENGTH.equalsIgnoreCase(key)
+ && connection instanceof HttpURLConnection) {
+ contentLength = header.getValue();
+ } else {
+ connection.setRequestProperty(key, header.getValue());
+ }
+ headers.append(key).append(": ").append(header.getValue()).append(EOL);
+ }
+ byte[] excerpt = null;
+ final InputStream body = request.getBody();
+ if (body != null) {
+ try {
+ if (contentLength != null) {
+ ((HttpURLConnection) connection)
+ .setFixedLengthStreamingMode(Integer.parseInt(contentLength));
+ }
+ connection.setDoOutput(true);
+ OutputStream output = connection.getOutputStream();
+ try {
+ final ExcerptInputStream ex = new ExcerptInputStream(body);
+ byte[] b = new byte[1024];
+ for (int n; 0 < (n = ex.read(b));) {
+ output.write(b, 0, n);
+ }
+ excerpt = ex.getExcerpt();
+ } finally {
+ output.close();
+ }
+ } finally {
+ body.close();
+ }
+ }
+ return new URLConnectionResponse(request, headers.toString(), excerpt, connection);
+ }
+
+ private static final String EOL = HttpResponseMessage.EOL;
+
+}
diff --git a/core/src/main/java/net/oauth/client/URLConnectionResponse.java b/core/src/main/java/net/oauth/client/URLConnectionResponse.java
new file mode 100755
index 0000000..4061ca8
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/URLConnectionResponse.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 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.client;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.HttpURLConnection;
+import java.net.URLConnection;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import net.oauth.OAuth;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+
+/**
+ * The response part of a URLConnection, encapsulated as an OAuthMessage.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class URLConnectionResponse extends HttpResponseMessage {
+
+ /**
+ * Construct an OAuthMessage from the HTTP response, including parameters
+ * from OAuth WWW-Authenticate headers and the body. The header parameters
+ * come first, followed by the ones from the response body.
+ */
+ public URLConnectionResponse(HttpMessage request, String requestHeaders,
+ byte[] requestExcerpt, URLConnection connection) throws IOException {
+ super(request.method, request.url);
+ this.requestHeaders = requestHeaders;
+ this.requestExcerpt = requestExcerpt;
+ this.requestEncoding = request.getContentCharset();
+ this.connection = connection;
+ this.headers.addAll(getHeaders());
+ }
+
+ private final String requestHeaders;
+ private final byte[] requestExcerpt;
+ private final String requestEncoding;
+ private final URLConnection connection;
+
+ @Override
+ public int getStatusCode() throws IOException {
+ if (connection instanceof HttpURLConnection) {
+ return ((HttpURLConnection) connection).getResponseCode();
+ }
+ return STATUS_OK;
+ }
+
+ @Override
+ public InputStream openBody() {
+ try {
+ return connection.getInputStream();
+ } catch (IOException ohWell) {
+ }
+ return null;
+ }
+
+ private List<Map.Entry<String, String>> getHeaders() {
+ List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>();
+ boolean foundContentType = false;
+ String value;
+ for (int i = 0; (value = connection.getHeaderField(i)) != null; ++i) {
+ String name = connection.getHeaderFieldKey(i);
+ if (name != null) {
+ headers.add(new OAuth.Parameter(name, value));
+ if (CONTENT_TYPE.equalsIgnoreCase(name)) {
+ foundContentType = true;
+ }
+ }
+ }
+ if (!foundContentType) {
+ headers.add(new OAuth.Parameter(CONTENT_TYPE, connection
+ .getContentType()));
+ }
+ return headers;
+ }
+ /** Return a complete description of the HTTP exchange. */
+ @Override
+ public void dump(Map<String, Object> into) throws IOException {
+ super.dump(into);
+ {
+ StringBuilder request = new StringBuilder(requestHeaders);
+ request.append(EOL);
+ if (requestExcerpt != null) {
+ request.append(new String(requestExcerpt, requestEncoding));
+ }
+ into.put(REQUEST, request.toString());
+ }
+ {
+ HttpURLConnection http = (connection instanceof HttpURLConnection) ? (HttpURLConnection) connection
+ : null;
+ StringBuilder response = new StringBuilder();
+ String value;
+ for (int i = 0; (value = connection.getHeaderField(i)) != null; ++i) {
+ String name = connection.getHeaderFieldKey(i);
+ if (i == 0 && name != null && http != null) {
+ String firstLine = "HTTP " + getStatusCode();
+ String message = http.getResponseMessage();
+ if (message != null) {
+ firstLine += (" " + message);
+ }
+ response.append(firstLine).append(EOL);
+ }
+ if (name != null) {
+ response.append(name).append(": ");
+ name = name.toLowerCase();
+ }
+ response.append(value).append(EOL);
+ }
+ response.append(EOL);
+ if (body != null) {
+ response.append(new String(((ExcerptInputStream) body)
+ .getExcerpt(), getContentCharset()));
+ }
+ into.put(HttpMessage.RESPONSE, response.toString());
+ }
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/client/httpclient4/HttpClient4.java b/core/src/main/java/net/oauth/client/httpclient4/HttpClient4.java
new file mode 100755
index 0000000..3376cc8
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/httpclient4/HttpClient4.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright 2008 Sean Sullivan
+ *
+ * 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.client.httpclient4;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.Map;
+import net.oauth.client.ExcerptInputStream;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpDelete;
+import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpPut;
+import org.apache.http.client.methods.HttpRequestBase;
+import org.apache.http.client.params.ClientPNames;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.entity.InputStreamEntity;
+import org.apache.http.impl.client.DefaultHttpClient;
+import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
+import org.apache.http.params.HttpParams;
+
+/**
+ * Utility methods for an OAuth client based on the <a
+ * href="http://hc.apache.org">Apache HttpClient</a>.
+ *
+ * @author Sean Sullivan
+ * @hide
+ */
+public class HttpClient4 implements net.oauth.http.HttpClient {
+
+ public HttpClient4() {
+ this(SHARED_CLIENT);
+ }
+
+ public HttpClient4(HttpClientPool clientPool) {
+ this.clientPool = clientPool;
+ }
+
+ private final HttpClientPool clientPool;
+
+ public HttpResponseMessage execute(HttpMessage request) throws IOException {
+ final String method = request.method;
+ final String url = request.url.toExternalForm();
+ final InputStream body = request.getBody();
+ final boolean isDelete = DELETE.equalsIgnoreCase(method);
+ final boolean isPost = POST.equalsIgnoreCase(method);
+ final boolean isPut = PUT.equalsIgnoreCase(method);
+ byte[] excerpt = null;
+ HttpRequestBase httpRequest;
+ if (isPost || isPut) {
+ HttpEntityEnclosingRequestBase entityEnclosingMethod =
+ isPost ? new HttpPost(url) : new HttpPut(url);
+ if (body != null) {
+ ExcerptInputStream e = new ExcerptInputStream(body);
+ excerpt = e.getExcerpt();
+ String length = request.removeHeaders(HttpMessage.CONTENT_LENGTH);
+ entityEnclosingMethod.setEntity(new InputStreamEntity(e,
+ (length == null) ? -1 : Long.parseLong(length)));
+ }
+ httpRequest = entityEnclosingMethod;
+ } else if (isDelete) {
+ httpRequest = new HttpDelete(url);
+ } else {
+ httpRequest = new HttpGet(url);
+ }
+ for (Map.Entry<String, String> header : request.headers) {
+ httpRequest.addHeader(header.getKey(), header.getValue());
+ }
+ HttpClient client = clientPool.getHttpClient(new URL(httpRequest.getURI().toString()));
+ client.getParams().setBooleanParameter(ClientPNames.HANDLE_REDIRECTS, false);
+ HttpResponse httpResponse = client.execute(httpRequest);
+ return new HttpMethodResponse(httpRequest, httpResponse, excerpt, request.getContentCharset());
+ }
+
+ private static final HttpClientPool SHARED_CLIENT = new SingleClient();
+
+ /**
+ * A pool that simply shares a single HttpClient. An HttpClient owns a pool
+ * of TCP connections. So, callers that share an HttpClient will share
+ * connections. Sharing improves performance (by avoiding the overhead of
+ * creating connections) and uses fewer resources in the client and its
+ * servers.
+ */
+ private static class SingleClient implements HttpClientPool
+ {
+ SingleClient()
+ {
+ HttpClient client = new DefaultHttpClient();
+ ClientConnectionManager mgr = client.getConnectionManager();
+ if (!(mgr instanceof ThreadSafeClientConnManager)) {
+ HttpParams params = client.getParams();
+ client = new DefaultHttpClient(new ThreadSafeClientConnManager(params,
+ mgr.getSchemeRegistry()), params);
+ }
+ this.client = client;
+ }
+
+ private final HttpClient client;
+
+ public HttpClient getHttpClient(URL server)
+ {
+ return client;
+ }
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/client/httpclient4/HttpClientPool.java b/core/src/main/java/net/oauth/client/httpclient4/HttpClientPool.java
new file mode 100755
index 0000000..90f9ade
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/httpclient4/HttpClientPool.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2008 Sean Sullivan
+ *
+ * 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.client.httpclient4;
+
+import java.net.URL;
+
+/**
+ *
+ * A source of Apache HttpClient 4 objects.
+ *
+ * This class relies on <a href="http://hc.apache.org">Apache HttpClient</a>
+ * version 4.
+ *
+ * @author Sean Sullivan
+ * @hide
+ */
+public interface HttpClientPool {
+
+ /** Get the appropriate HttpClient for sending a request to the given URL. */
+ public org.apache.http.client.HttpClient getHttpClient(URL server);
+
+}
diff --git a/core/src/main/java/net/oauth/client/httpclient4/HttpMethodResponse.java b/core/src/main/java/net/oauth/client/httpclient4/HttpMethodResponse.java
new file mode 100755
index 0000000..1f7b747
--- /dev/null
+++ b/core/src/main/java/net/oauth/client/httpclient4/HttpMethodResponse.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2008 Sean Sullivan
+ *
+ * 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.client.httpclient4;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import net.oauth.OAuth;
+import net.oauth.client.ExcerptInputStream;
+import net.oauth.http.HttpMessage;
+import net.oauth.http.HttpResponseMessage;
+import org.apache.http.Header;
+import org.apache.http.HttpEntityEnclosingRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpRequestBase;
+
+/**
+ * An HttpResponse, encapsulated as an OAuthMessage.
+ *
+ * This class relies on <a href="http://hc.apache.org">Apache HttpClient</a>
+ * version 4.
+ *
+ * @author Sean Sullivan
+ * @hide
+ */
+public class HttpMethodResponse extends HttpResponseMessage
+{
+
+ /**
+ * Construct an OAuthMessage from the HTTP response, including parameters
+ * from OAuth WWW-Authenticate headers and the body. The header parameters
+ * come first, followed by the ones from the response body.
+ */
+ public HttpMethodResponse(HttpRequestBase request, HttpResponse response, byte[] requestBody,
+ String requestEncoding) throws IOException
+ {
+ super(request.getMethod(), new URL(request.getURI().toString()));
+ this.httpRequest = request;
+ this.httpResponse = response;
+ this.requestBody = requestBody;
+ this.requestEncoding = requestEncoding;
+ this.headers.addAll(getHeaders());
+ }
+
+ private final HttpRequestBase httpRequest;
+ private final HttpResponse httpResponse;
+ private final byte[] requestBody;
+ private final String requestEncoding;
+
+ @Override
+ public int getStatusCode()
+ {
+ return httpResponse.getStatusLine().getStatusCode();
+ }
+
+ @Override
+ public InputStream openBody() throws IOException
+ {
+ return httpResponse.getEntity().getContent();
+ }
+
+ private List<Map.Entry<String, String>> getHeaders()
+ {
+ List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>();
+ Header[] allHeaders = httpResponse.getAllHeaders();
+ if (allHeaders != null) {
+ for (Header header : allHeaders) {
+ headers.add(new OAuth.Parameter(header.getName(), header.getValue()));
+ }
+ }
+ return headers;
+ }
+
+ /** Return a complete description of the HTTP exchange. */
+ @Override
+ public void dump(Map<String, Object> into) throws IOException
+ {
+ super.dump(into);
+ {
+ StringBuilder request = new StringBuilder(httpRequest.getMethod());
+ request.append(" ").append(httpRequest.getURI().getPath());
+ String query = httpRequest.getURI().getQuery();
+ if (query != null && query.length() > 0) {
+ request.append("?").append(query);
+ }
+ request.append(EOL);
+ for (Header header : httpRequest.getAllHeaders()) {
+ request.append(header.getName()).append(": ").append(header.getValue()).append(EOL);
+ }
+ if (httpRequest instanceof HttpEntityEnclosingRequest) {
+ HttpEntityEnclosingRequest r = (HttpEntityEnclosingRequest) httpRequest;
+ long contentLength = r.getEntity().getContentLength();
+ if (contentLength >= 0) {
+ request.append("Content-Length: ").append(contentLength).append(EOL);
+ }
+ }
+ request.append(EOL);
+ if (requestBody != null) {
+ request.append(new String(requestBody, requestEncoding));
+ }
+ into.put(REQUEST, request.toString());
+ }
+ {
+ StringBuilder response = new StringBuilder();
+ String value = httpResponse.getStatusLine().toString();
+ response.append(value).append(EOL);
+ for (Header header : httpResponse.getAllHeaders()) {
+ String name = header.getName();
+ value = header.getValue();
+ response.append(name).append(": ").append(value).append(EOL);
+ }
+ response.append(EOL);
+ if (body != null) {
+ response.append(new String(((ExcerptInputStream) body).getExcerpt(),
+ getContentCharset()));
+ }
+ into.put(HttpMessage.RESPONSE, response.toString());
+ }
+ }
+}
diff --git a/core/src/main/java/net/oauth/consumer.properties.sample b/core/src/main/java/net/oauth/consumer.properties.sample
new file mode 100755
index 0000000..ea26c90
--- /dev/null
+++ b/core/src/main/java/net/oauth/consumer.properties.sample
@@ -0,0 +1,16 @@
+# NamedConsumerPool can gets consumer configuration parameters from a file like this.
+
+ma.gnolia.consumerKey: - Your key here -
+ma.gnolia.consumerSecret: - Your secret here -
+ma.gnolia.serviceProvider.requestTokenURL: http://ma.gnolia.com/oauth/get_request_token
+ma.gnolia.serviceProvider.userAuthorizationURL: http://ma.gnolia.com/oauth/authorize
+ma.gnolia.serviceProvider.accessTokenURL: http://ma.gnolia.com/oauth/get_access_token
+
+twitter.consumerKey: - Your key here -
+twitter.consumerSecret: - Your secret here -
+twitter.callbackURL: - Your URL here -
+twitter.consumer.oauth_signature_method: PLAINTEXT
+# There can be more consumer properties.
+twitter.serviceProvider.requestTokenURL: http://twitter.com/oauth/request_token
+twitter.serviceProvider.userAuthorizationURL: http://twitter.com/oauth/authorize
+twitter.serviceProvider.accessTokenURL: http://twitter.com/oauth/access_token
diff --git a/core/src/main/java/net/oauth/http/HttpClient.java b/core/src/main/java/net/oauth/http/HttpClient.java
new file mode 100755
index 0000000..b964f96
--- /dev/null
+++ b/core/src/main/java/net/oauth/http/HttpClient.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 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.http;
+
+import java.io.IOException;
+import net.oauth.OAuthMessage;
+
+/**
+ * @hide
+ */
+public interface HttpClient
+{
+ /**
+ * Send an HTTP request and return the response.
+ * <p>
+ * Don't follow redirects. If a redirect response is received, simply return
+ * it (with a statusCode and LOCATION header).
+ */
+ HttpResponseMessage execute(HttpMessage request) throws IOException;
+
+ static final String GET = OAuthMessage.GET;
+ static final String POST = OAuthMessage.POST;
+ static final String PUT = OAuthMessage.PUT;
+ static final String DELETE = OAuthMessage.DELETE;
+
+}
diff --git a/core/src/main/java/net/oauth/http/HttpMessage.java b/core/src/main/java/net/oauth/http/HttpMessage.java
new file mode 100755
index 0000000..8f3ee5f
--- /dev/null
+++ b/core/src/main/java/net/oauth/http/HttpMessage.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright 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.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import net.oauth.client.ExcerptInputStream;
+
+/**
+ * An HTTP request or response.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public class HttpMessage
+{
+
+ public HttpMessage()
+ {
+ this(null, null);
+ }
+
+ public HttpMessage(String method, URL url)
+ {
+ this(method, url, null);
+ }
+
+ public HttpMessage(String method, URL url, InputStream body)
+ {
+ this.method = method;
+ this.url = url;
+ this.body = body;
+ }
+
+ public String method;
+ public URL url;
+ public final List<Map.Entry<String, String>> headers = new ArrayList<Map.Entry<String, String>>();
+ protected InputStream body = null;
+
+ /**
+ * Get the value of the last header of the given name. The name is
+ * case-insensitive.
+ */
+ public final String getHeader(String name)
+ {
+ String value = null;
+ for (Map.Entry<String, String> header : headers) {
+ if (equalsIgnoreCase(name, header.getKey())) {
+ value = header.getValue();
+ }
+ }
+ return value;
+ }
+
+ /**
+ * Remove all headers of the given name. The name is case insensitive.
+ *
+ * @return the value of the last header with that name, or null to indicate
+ * there was no such header
+ */
+ public String removeHeaders(String name)
+ {
+ String value = null;
+ for (Iterator<Map.Entry<String, String>> i = headers.iterator(); i.hasNext();) {
+ Map.Entry<String, String> header = i.next();
+ if (equalsIgnoreCase(name, header.getKey())) {
+ value = header.getValue();
+ i.remove();
+ }
+ }
+ return value;
+ }
+
+ public final String getContentCharset()
+ {
+ return getCharset(getHeader(CONTENT_TYPE));
+ }
+
+ public final InputStream getBody() throws IOException
+ {
+ if (body == null) {
+ InputStream raw = openBody();
+ if (raw != null) {
+ body = new ExcerptInputStream(raw);
+ }
+ }
+ return body;
+ }
+
+ protected InputStream openBody() throws IOException
+ {
+ return null;
+ }
+
+ /** Put a description of this message and its origins into the given Map. */
+ public void dump(Map<String, Object> into) throws IOException
+ {
+ }
+
+ private static boolean equalsIgnoreCase(String x, String y)
+ {
+ if (x == null)
+ return y == null;
+ else
+ return x.equalsIgnoreCase(y);
+ }
+
+ private static final String getCharset(String mimeType)
+ {
+ if (mimeType != null) {
+ Matcher m = CHARSET.matcher(mimeType);
+ if (m.find()) {
+ String charset = m.group(1);
+ if (charset.length() >= 2 && charset.charAt(0) == '"'
+ && charset.charAt(charset.length() - 1) == '"') {
+ charset = charset.substring(1, charset.length() - 1);
+ charset = charset.replace("\\\"", "\"");
+ }
+ return charset;
+ }
+ }
+ return DEFAULT_CHARSET;
+ }
+
+ /** The name of a dump entry whose value is the HTTP request. */
+ public static final String REQUEST = "HTTP request";
+
+ /** The name of a dump entry whose value is the HTTP response. */
+ public static final String RESPONSE = "HTTP response";
+
+ public static final String ACCEPT_ENCODING = "Accept-Encoding";
+ public static final String CONTENT_ENCODING = "Content-Encoding";
+ public static final String CONTENT_LENGTH = "Content-Length";
+ public static final String CONTENT_TYPE = "Content-Type";
+ public static final String DEFAULT_CHARSET = "ISO-8859-1";
+
+ private static final Pattern CHARSET = Pattern
+ .compile("; *charset *= *([^;\"]*|\"([^\"]|\\\\\")*\")(;|$)");
+
+}
diff --git a/core/src/main/java/net/oauth/http/HttpMessageDecoder.java b/core/src/main/java/net/oauth/http/HttpMessageDecoder.java
new file mode 100755
index 0000000..0db1a42
--- /dev/null
+++ b/core/src/main/java/net/oauth/http/HttpMessageDecoder.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 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.http;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Map;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.InflaterInputStream;
+
+/**
+ * A decorator that handles Content-Encoding.
+ * @hide
+ */
+public class HttpMessageDecoder extends HttpResponseMessage {
+
+ /**
+ * Decode the given message if necessary and possible.
+ *
+ * @return a decorator that decodes the body of the given message; or the
+ * given message if this class can't decode it.
+ */
+ public static HttpResponseMessage decode(HttpResponseMessage message)
+ throws IOException {
+ if (message != null) {
+ String encoding = getEncoding(message);
+ if (encoding != null) {
+ return new HttpMessageDecoder(message, encoding);
+ }
+ }
+ return message;
+ }
+
+ public static final String GZIP = "gzip";
+ public static final String DEFLATE = "deflate";
+ public static final String ACCEPTED = GZIP + "," + DEFLATE;
+
+ private static String getEncoding(HttpMessage message) {
+ String encoding = message.getHeader(CONTENT_ENCODING);
+ if (encoding == null) {
+ // That's easy.
+ } else if (GZIP.equalsIgnoreCase(encoding)
+ || ("x-" + GZIP).equalsIgnoreCase(encoding)) {
+ return GZIP;
+ } else if (DEFLATE.equalsIgnoreCase(encoding)) {
+ return DEFLATE;
+ }
+ return null;
+ }
+
+ private HttpMessageDecoder(HttpResponseMessage in, String encoding)
+ throws IOException {
+ super(in.method, in.url);
+ this.headers.addAll(in.headers);
+ removeHeaders(CONTENT_ENCODING); // handled here
+ removeHeaders(CONTENT_LENGTH); // unpredictable
+ InputStream body = in.getBody();
+ if (body != null) {
+ if (encoding == GZIP) {
+ body = new GZIPInputStream(body);
+ } else if (encoding == DEFLATE) {
+ body = new InflaterInputStream(body);
+ } else {
+ assert false;
+ }
+ }
+ this.body = body;
+ this.in = in;
+ }
+
+ private final HttpResponseMessage in;
+
+ @Override
+ public void dump(Map<String, Object> into) throws IOException {
+ in.dump(into);
+ }
+
+ @Override
+ public int getStatusCode() throws IOException {
+ return in.getStatusCode();
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/http/HttpResponseMessage.java b/core/src/main/java/net/oauth/http/HttpResponseMessage.java
new file mode 100755
index 0000000..8ad5816
--- /dev/null
+++ b/core/src/main/java/net/oauth/http/HttpResponseMessage.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 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.http;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.Map;
+
+/**
+ * An HTTP response.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public abstract class HttpResponseMessage extends HttpMessage {
+
+ protected HttpResponseMessage(String method, URL url) {
+ super(method, url);
+ }
+
+ @Override
+ public void dump(Map<String, Object> into) throws IOException {
+ super.dump(into);
+ into.put(STATUS_CODE, Integer.valueOf(getStatusCode()));
+ String location = getHeader(LOCATION);
+ if (location != null) {
+ into.put(LOCATION, location);
+ }
+ }
+
+ public abstract int getStatusCode() throws IOException;
+
+ /** The name of a dump entry whose value is the response Location header. */
+ public static final String LOCATION = "Location";
+
+ /** The name of a dump entry whose value is the HTTP status code. */
+ public static final String STATUS_CODE = "HTTP status";
+
+ /** The statusCode that indicates a normal outcome. */
+ public static final int STATUS_OK = 200;
+
+ /** The standard end-of-line marker in an HTTP message. */
+ public static final String EOL = "\r\n";
+
+}
diff --git a/core/src/main/java/net/oauth/signature/HMAC_SHA1.java b/core/src/main/java/net/oauth/signature/HMAC_SHA1.java
new file mode 100755
index 0000000..dee72fd
--- /dev/null
+++ b/core/src/main/java/net/oauth/signature/HMAC_SHA1.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2007 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.signature;
+
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.util.Arrays;
+
+import javax.crypto.Mac;
+import javax.crypto.SecretKey;
+import javax.crypto.spec.SecretKeySpec;
+
+import net.oauth.OAuth;
+import net.oauth.OAuthException;
+
+/**
+ * @author John Kristian
+ * @hide
+ */
+class HMAC_SHA1 extends OAuthSignatureMethod {
+
+ @Override
+ protected String getSignature(String baseString) throws OAuthException {
+ try {
+ String signature = base64Encode(computeSignature(baseString));
+ return signature;
+ } catch (GeneralSecurityException e) {
+ throw new OAuthException(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new OAuthException(e);
+ }
+ }
+
+ @Override
+ protected boolean isValid(String signature, String baseString)
+ throws OAuthException {
+ try {
+ byte[] expected = computeSignature(baseString);
+ byte[] actual = decodeBase64(signature);
+ return Arrays.equals(expected, actual);
+ } catch (GeneralSecurityException e) {
+ throw new OAuthException(e);
+ } catch (UnsupportedEncodingException e) {
+ throw new OAuthException(e);
+ }
+ }
+
+ private byte[] computeSignature(String baseString)
+ throws GeneralSecurityException, UnsupportedEncodingException {
+ SecretKey key = null;
+ synchronized (this) {
+ if (this.key == null) {
+ String keyString = OAuth.percentEncode(getConsumerSecret())
+ + '&' + OAuth.percentEncode(getTokenSecret());
+ byte[] keyBytes = keyString.getBytes(ENCODING);
+ this.key = new SecretKeySpec(keyBytes, MAC_NAME);
+ }
+ key = this.key;
+ }
+ Mac mac = Mac.getInstance(MAC_NAME);
+ mac.init(key);
+ byte[] text = baseString.getBytes(ENCODING);
+ return mac.doFinal(text);
+ }
+
+ /** ISO-8859-1 or US-ASCII would work, too. */
+ private static final String ENCODING = OAuth.ENCODING;
+
+ private static final String MAC_NAME = "HmacSHA1";
+
+ private SecretKey key = null;
+
+ @Override
+ public void setConsumerSecret(String consumerSecret) {
+ synchronized (this) {
+ key = null;
+ }
+ super.setConsumerSecret(consumerSecret);
+ }
+
+ @Override
+ public void setTokenSecret(String tokenSecret) {
+ synchronized (this) {
+ key = null;
+ }
+ super.setTokenSecret(tokenSecret);
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/signature/OAuthSignatureMethod.java b/core/src/main/java/net/oauth/signature/OAuthSignatureMethod.java
new file mode 100755
index 0000000..967153d
--- /dev/null
+++ b/core/src/main/java/net/oauth/signature/OAuthSignatureMethod.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2007 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.signature;
+
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import net.oauth.OAuth;
+import net.oauth.OAuthAccessor;
+import net.oauth.OAuthConsumer;
+import net.oauth.OAuthException;
+import net.oauth.OAuthMessage;
+import net.oauth.OAuthProblemException;
+import org.apache.commons.codec.binary.Base64;
+
+/**
+ * A pair of algorithms for computing and verifying an OAuth digital signature.
+ *
+ * @author John Kristian
+ * @hide
+ */
+public abstract class OAuthSignatureMethod {
+
+ /** Add a signature to the message.
+ * @throws URISyntaxException
+ * @throws IOException */
+ public void sign(OAuthMessage message)
+ throws OAuthException, IOException, URISyntaxException {
+ message.addParameter(new OAuth.Parameter("oauth_signature",
+ getSignature(message)));
+ }
+
+ /**
+ * Check whether the message has a valid signature.
+ * @throws URISyntaxException
+ *
+ * @throws OAuthProblemException
+ * the signature is invalid
+ */
+ public void validate(OAuthMessage message)
+ throws IOException, OAuthException, URISyntaxException {
+ message.requireParameters("oauth_signature");
+ String signature = message.getSignature();
+ String baseString = getBaseString(message);
+ if (!isValid(signature, baseString)) {
+ OAuthProblemException problem = new OAuthProblemException(
+ "signature_invalid");
+ problem.setParameter("oauth_signature", signature);
+ problem.setParameter("oauth_signature_base_string", baseString);
+ problem.setParameter("oauth_signature_method", message
+ .getSignatureMethod());
+ throw problem;
+ }
+ }
+
+ protected String getSignature(OAuthMessage message)
+ throws OAuthException, IOException, URISyntaxException {
+ String baseString = getBaseString(message);
+ String signature = getSignature(baseString);
+ // Logger log = Logger.getLogger(getClass().getName());
+ // if (log.isLoggable(Level.FINE)) {
+ // log.fine(signature + "=getSignature(" + baseString + ")");
+ // }
+ return signature;
+ }
+
+ protected void initialize(String name, OAuthAccessor accessor)
+ throws OAuthException {
+ String secret = accessor.consumer.consumerSecret;
+ if (name.endsWith(_ACCESSOR)) {
+ // This code supports the 'Accessor Secret' extensions
+ // described in http://oauth.pbwiki.com/AccessorSecret
+ final String key = OAuthConsumer.ACCESSOR_SECRET;
+ Object accessorSecret = accessor.getProperty(key);
+ if (accessorSecret == null) {
+ accessorSecret = accessor.consumer.getProperty(key);
+ }
+ if (accessorSecret != null) {
+ secret = accessorSecret.toString();
+ }
+ }
+ if (secret == null) {
+ secret = "";
+ }
+ setConsumerSecret(secret);
+ }
+
+ public static final String _ACCESSOR = "-Accessor";
+
+ /** Compute the signature for the given base string. */
+ protected abstract String getSignature(String baseString) throws OAuthException;
+
+ /** Decide whether the signature is valid. */
+ protected abstract boolean isValid(String signature, String baseString)
+ throws OAuthException;
+
+ private String consumerSecret;
+
+ private String tokenSecret;
+
+ protected String getConsumerSecret() {
+ return consumerSecret;
+ }
+
+ protected void setConsumerSecret(String consumerSecret) {
+ this.consumerSecret = consumerSecret;
+ }
+
+ public String getTokenSecret() {
+ return tokenSecret;
+ }
+
+ public void setTokenSecret(String tokenSecret) {
+ this.tokenSecret = tokenSecret;
+ }
+
+ public static String getBaseString(OAuthMessage message)
+ throws IOException, URISyntaxException {
+ List<Map.Entry<String, String>> parameters;
+ String url = message.URL;
+ int q = url.indexOf('?');
+ if (q < 0) {
+ parameters = message.getParameters();
+ } else {
+ // Combine the URL query string with the other parameters:
+ parameters = new ArrayList<Map.Entry<String, String>>();
+ parameters.addAll(OAuth.decodeForm(message.URL.substring(q + 1)));
+ parameters.addAll(message.getParameters());
+ url = url.substring(0, q);
+ }
+ return OAuth.percentEncode(message.method.toUpperCase()) + '&'
+ + OAuth.percentEncode(normalizeUrl(url)) + '&'
+ + OAuth.percentEncode(normalizeParameters(parameters));
+ }
+
+ protected static String normalizeUrl(String url) throws URISyntaxException {
+ URI uri = new URI(url);
+ String scheme = uri.getScheme().toLowerCase();
+ String authority = uri.getAuthority().toLowerCase();
+ boolean dropPort = (scheme.equals("http") && uri.getPort() == 80)
+ || (scheme.equals("https") && uri.getPort() == 443);
+ if (dropPort) {
+ // find the last : in the authority
+ int index = authority.lastIndexOf(":");
+ if (index >= 0) {
+ authority = authority.substring(0, index);
+ }
+ }
+ String path = uri.getRawPath();
+ if (path == null || path.length() <= 0) {
+ path = "/"; // conforms to RFC 2616 section 3.2.2
+ }
+ // we know that there is no query and no fragment here.
+ return scheme + "://" + authority + path;
+ }
+
+ protected static String normalizeParameters(
+ Collection<? extends Map.Entry> parameters) throws IOException {
+ if (parameters == null) {
+ return "";
+ }
+ List<ComparableParameter> p = new ArrayList<ComparableParameter>(
+ parameters.size());
+ for (Map.Entry parameter : parameters) {
+ if (!"oauth_signature".equals(parameter.getKey())) {
+ p.add(new ComparableParameter(parameter));
+ }
+ }
+ Collections.sort(p);
+ return OAuth.formEncode(getParameters(p));
+ }
+
+ public static byte[] decodeBase64(String s) {
+ return BASE64.decode(s.getBytes());
+ }
+
+ public static String base64Encode(byte[] b) {
+ return new String(BASE64.encode(b));
+ }
+
+ private static final Base64 BASE64 = new Base64();
+
+ public static OAuthSignatureMethod newSigner(OAuthMessage message,
+ OAuthAccessor accessor) throws IOException, OAuthException {
+ message.requireParameters(OAuth.OAUTH_SIGNATURE_METHOD);
+ OAuthSignatureMethod signer = newMethod(message.getSignatureMethod(),
+ accessor);
+ signer.setTokenSecret(accessor.tokenSecret);
+ return signer;
+ }
+
+ /** The factory for signature methods. */
+ public static OAuthSignatureMethod newMethod(String name,
+ OAuthAccessor accessor) throws OAuthException {
+ try {
+ Class methodClass = NAME_TO_CLASS.get(name);
+ if (methodClass != null) {
+ OAuthSignatureMethod method = (OAuthSignatureMethod) methodClass
+ .newInstance();
+ method.initialize(name, accessor);
+ return method;
+ }
+ OAuthProblemException problem = new OAuthProblemException(
+ "signature_method_rejected");
+ String acceptable = OAuth.percentEncode(NAME_TO_CLASS.keySet());
+ if (acceptable.length() > 0) {
+ problem.setParameter("oauth_acceptable_signature_methods",
+ acceptable.toString());
+ }
+ throw problem;
+ } catch (InstantiationException e) {
+ throw new OAuthException(e);
+ } catch (IllegalAccessException e) {
+ throw new OAuthException(e);
+ }
+ }
+
+ /**
+ * Subsequently, newMethod(name) will attempt to instantiate the given
+ * class, with no constructor parameters.
+ */
+ public static void registerMethodClass(String name, Class clazz) {
+ NAME_TO_CLASS.put(name, clazz);
+ }
+
+ private static final Map<String, Class> NAME_TO_CLASS = new ConcurrentHashMap<String, Class>();
+ static {
+ registerMethodClass("HMAC-SHA1", HMAC_SHA1.class);
+ registerMethodClass("PLAINTEXT", PLAINTEXT.class);
+ registerMethodClass("RSA-SHA1", RSA_SHA1.class);
+ registerMethodClass("HMAC-SHA1" + _ACCESSOR, HMAC_SHA1.class);
+ registerMethodClass("PLAINTEXT" + _ACCESSOR, PLAINTEXT.class);
+ }
+
+ /** An efficiently sortable wrapper around a parameter. */
+ private static class ComparableParameter implements
+ Comparable<ComparableParameter> {
+
+ ComparableParameter(Map.Entry value) {
+ this.value = value;
+ String n = toString(value.getKey());
+ String v = toString(value.getValue());
+ this.key = OAuth.percentEncode(n) + ' ' + OAuth.percentEncode(v);
+ // ' ' is used because it comes before any character
+ // that can appear in a percentEncoded string.
+ }
+
+ final Map.Entry value;
+
+ private final String key;
+
+ private static String toString(Object from) {
+ return (from == null) ? null : from.toString();
+ }
+
+ public int compareTo(ComparableParameter that) {
+ return this.key.compareTo(that.key);
+ }
+
+ @Override
+ public String toString() {
+ return key;
+ }
+
+ }
+
+ /** Retrieve the original parameters from a sorted collection. */
+ private static List<Map.Entry> getParameters(
+ Collection<ComparableParameter> parameters) {
+ if (parameters == null) {
+ return null;
+ }
+ List<Map.Entry> list = new ArrayList<Map.Entry>(parameters.size());
+ for (ComparableParameter parameter : parameters) {
+ list.add(parameter.value);
+ }
+ return list;
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/signature/PLAINTEXT.java b/core/src/main/java/net/oauth/signature/PLAINTEXT.java
new file mode 100755
index 0000000..910f903
--- /dev/null
+++ b/core/src/main/java/net/oauth/signature/PLAINTEXT.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2007 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.signature;
+
+import net.oauth.OAuth;
+import net.oauth.OAuthException;
+
+/**
+ * @author John Kristian
+ * @hide
+ */
+class PLAINTEXT extends OAuthSignatureMethod {
+
+ @Override
+ public String getSignature(String baseString) {
+ return getSignature();
+ }
+
+ @Override
+ protected boolean isValid(String signature, String baseString)
+ throws OAuthException {
+ return signature.equals(getSignature());
+ }
+
+ private synchronized String getSignature() {
+ if (signature == null) {
+ signature = OAuth.percentEncode(getConsumerSecret()) + '&'
+ + OAuth.percentEncode(getTokenSecret());
+ }
+ return signature;
+ }
+
+ private String signature = null;
+
+ @Override
+ public void setConsumerSecret(String consumerSecret) {
+ synchronized (this) {
+ signature = null;
+ }
+ super.setConsumerSecret(consumerSecret);
+ }
+
+ @Override
+ public void setTokenSecret(String tokenSecret) {
+ synchronized (this) {
+ signature = null;
+ }
+ super.setTokenSecret(tokenSecret);
+ }
+
+}
diff --git a/core/src/main/java/net/oauth/signature/RSA_SHA1.java b/core/src/main/java/net/oauth/signature/RSA_SHA1.java
new file mode 100755
index 0000000..0aa99f1
--- /dev/null
+++ b/core/src/main/java/net/oauth/signature/RSA_SHA1.java
@@ -0,0 +1,239 @@
+/*
+ * Copyright 2007 Google, 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.signature;
+
+import java.io.ByteArrayInputStream;
+import java.io.UnsupportedEncodingException;
+import java.security.GeneralSecurityException;
+import java.security.KeyFactory;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.security.Signature;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.EncodedKeySpec;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.X509EncodedKeySpec;
+
+import net.oauth.OAuth;
+import net.oauth.OAuthAccessor;
+import net.oauth.OAuthException;
+
+/**
+ * Class to handle RSA-SHA1 signatures on OAuth requests. A consumer
+ * that wishes to use public-key signatures on messages does not need
+ * a shared secret with the service provider, but it needs a private
+ * RSA signing key. You create it like this:
+ *
+ * OAuthConsumer c = new OAuthConsumer(callback_url, consumer_key,
+ * null, provider);
+ * c.setProperty(RSA_SHA1.PRIVATE_KEY, consumer_privateRSAKey);
+ *
+ * consumer_privateRSAKey must be an RSA signing key and
+ * of type java.security.PrivateKey, String, or byte[]. In the latter two
+ * cases, the key must be PKCS#8-encoded (byte[]) or PKCS#8-encoded and
+ * then Base64-encoded (String).
+ *
+ * A service provider that wishes to verify signatures made by such a
+ * consumer does not need a shared secret with the consumer, but it needs
+ * to know the consumer's public key. You create the necessary
+ * OAuthConsumer object (on the service provider's side) like this:
+ *
+ * OAuthConsumer c = new OAuthConsumer(callback_url, consumer_key,
+ * null, provider);
+ * c.setProperty(RSA_SHA1.PUBLIC_KEY, consumer_publicRSAKey);
+ *
+ * consumer_publicRSAKey must be the consumer's public RSAkey and
+ * of type java.security.PublicKey, String, or byte[]. In the latter two
+ * cases, the key must be X509-encoded (byte[]) or X509-encoded and
+ * then Base64-encoded (String).
+ *
+ * Alternatively, a service provider that wishes to verify signatures made
+ * by such a consumer can use a X509 certificate containing the consumer's
+ * public key. You create the necessary OAuthConsumer object (on the service
+ * provider's side) like this:
+ *
+ * OAuthConsumer c = new OAuthConsumer(callback_url, consumer_key,
+ * null, provider);
+ * c.setProperty(RSA_SHA1.X509_CERTIFICATE, consumer_cert);
+ *
+ * consumer_cert must be a X509 Certificate containing the consumer's public
+ * key and be of type java.security.cert.X509Certificate, String,
+ * or byte[]. In the latter two cases, the certificate must be DER-encoded
+ * (byte[]) or PEM-encoded (String).
+ *
+ * @author Dirk Balfanz
+ * @hide
+ *
+ */
+public class RSA_SHA1 extends OAuthSignatureMethod {
+
+ final static public String PRIVATE_KEY = "RSA-SHA1.PrivateKey";
+ final static public String PUBLIC_KEY = "RSA-SHA1.PublicKey";
+ final static public String X509_CERTIFICATE = "RSA-SHA1.X509Certificate";
+
+ private PrivateKey privateKey = null;
+ private PublicKey publicKey = null;
+
+ @Override
+ protected void initialize(String name, OAuthAccessor accessor)
+ throws OAuthException {
+ super.initialize(name, accessor);
+
+ Object privateKeyObject = accessor.consumer.getProperty(PRIVATE_KEY);
+ try {
+ if (privateKeyObject != null) {
+ if (privateKeyObject instanceof PrivateKey) {
+ privateKey = (PrivateKey)privateKeyObject;
+ } else if (privateKeyObject instanceof String) {
+ privateKey = getPrivateKeyFromPem((String)privateKeyObject);
+ } else if (privateKeyObject instanceof byte[]) {
+ privateKey = getPrivateKeyFromDer((byte[])privateKeyObject);
+ } else {
+ throw new IllegalArgumentException(
+ "Private key set through RSA_SHA1.PRIVATE_KEY must be of " +
+ "type PrivateKey, String, or byte[], and not " +
+ privateKeyObject.getClass().getName());
+ }
+ }
+
+ Object publicKeyObject = accessor.consumer.getProperty(PUBLIC_KEY);
+ if (publicKeyObject != null) {
+ if (publicKeyObject instanceof PublicKey) {
+ publicKey = (PublicKey)publicKeyObject;
+ } else if (publicKeyObject instanceof String) {
+ publicKey = getPublicKeyFromPem((String)publicKeyObject);
+ } else if (publicKeyObject instanceof byte[]) {
+ publicKey = getPublicKeyFromDer((byte[])publicKeyObject);
+ } else {
+ throw new IllegalArgumentException(
+ "Public key set through RSA_SHA1.PRIVATE_KEY must be of " +
+ "type PublicKey, String, or byte[], and not " +
+ publicKeyObject.getClass().getName());
+ }
+ } else { // public key was null. perhaps they gave us a X509 cert.
+ Object certObject = accessor.consumer.getProperty(X509_CERTIFICATE);
+ if (certObject != null) {
+ if (certObject instanceof X509Certificate) {
+ publicKey = ((X509Certificate) certObject).getPublicKey();
+ } else if (certObject instanceof String) {
+ publicKey = getPublicKeyFromPemCert((String)certObject);
+ } else if (certObject instanceof byte[]) {
+ publicKey = getPublicKeyFromDerCert((byte[])certObject);
+ } else {
+ throw new IllegalArgumentException(
+ "X509Certificate set through RSA_SHA1.X509_CERTIFICATE" +
+ " must be of type X509Certificate, String, or byte[]," +
+ " and not " + certObject.getClass().getName());
+ }
+ }
+ }
+ } catch (GeneralSecurityException e) {
+ throw new OAuthException(e);
+ }
+ }
+
+ private PublicKey getPublicKeyFromPemCert(String certObject)
+ throws GeneralSecurityException {
+ CertificateFactory fac = CertificateFactory.getInstance("X509");
+ ByteArrayInputStream in = new ByteArrayInputStream(certObject.getBytes());
+ X509Certificate cert = (X509Certificate)fac.generateCertificate(in);
+ return cert.getPublicKey();
+ }
+
+ private PublicKey getPublicKeyFromDerCert(byte[] certObject)
+ throws GeneralSecurityException {
+ CertificateFactory fac = CertificateFactory.getInstance("X509");
+ ByteArrayInputStream in = new ByteArrayInputStream(certObject);
+ X509Certificate cert = (X509Certificate)fac.generateCertificate(in);
+ return cert.getPublicKey();
+ }
+
+ private PublicKey getPublicKeyFromDer(byte[] publicKeyObject)
+ throws GeneralSecurityException {
+ KeyFactory fac = KeyFactory.getInstance("RSA");
+ EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(publicKeyObject);
+ return fac.generatePublic(pubKeySpec);
+ }
+
+ private PublicKey getPublicKeyFromPem(String publicKeyObject)
+ throws GeneralSecurityException {
+ return getPublicKeyFromDer(decodeBase64(publicKeyObject));
+ }
+
+ private PrivateKey getPrivateKeyFromDer(byte[] privateKeyObject)
+ throws GeneralSecurityException {
+ KeyFactory fac = KeyFactory.getInstance("RSA");
+ EncodedKeySpec privKeySpec = new PKCS8EncodedKeySpec(privateKeyObject);
+ return fac.generatePrivate(privKeySpec);
+ }
+
+ private PrivateKey getPrivateKeyFromPem(String privateKeyObject)
+ throws GeneralSecurityException {
+ return getPrivateKeyFromDer(decodeBase64(privateKeyObject));
+ }
+
+ @Override
+ protected String getSignature(String baseString) throws OAuthException {
+ try {
+ byte[] signature = sign(baseString.getBytes(OAuth.ENCODING));
+ return base64Encode(signature);
+ } catch (UnsupportedEncodingException e) {
+ throw new OAuthException(e);
+ } catch (GeneralSecurityException e) {
+ throw new OAuthException(e);
+ }
+ }
+
+ @Override
+ protected boolean isValid(String signature, String baseString)
+ throws OAuthException {
+ try {
+ return verify(decodeBase64(signature),
+ baseString.getBytes(OAuth.ENCODING));
+ } catch (UnsupportedEncodingException e) {
+ throw new OAuthException(e);
+ } catch (GeneralSecurityException e) {
+ throw new OAuthException(e);
+ }
+ }
+
+ private byte[] sign(byte[] message) throws GeneralSecurityException {
+ if (privateKey == null) {
+ throw new IllegalStateException("need to set private key with " +
+ "OAuthConsumer.setProperty when " +
+ "generating RSA-SHA1 signatures.");
+ }
+ Signature signer = Signature.getInstance("SHA1withRSA");
+ signer.initSign(privateKey);
+ signer.update(message);
+ return signer.sign();
+ }
+
+ private boolean verify(byte[] signature, byte[] message)
+ throws GeneralSecurityException {
+ if (publicKey == null) {
+ throw new IllegalStateException("need to set public key with " +
+ " OAuthConsumer.setProperty when " +
+ "verifying RSA-SHA1 signatures.");
+ }
+ Signature verifier = Signature.getInstance("SHA1withRSA");
+ verifier.initVerify(publicKey);
+ verifier.update(message);
+ return verifier.verify(signature);
+ }
+}