| /* |
| * 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); |
| } |
| } |