/* * 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 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 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 decodeForm(String form) { List list = new ArrayList(); 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 newMap(Iterable from) { Map map = new HashMap(); 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 newList(String... parameters) { List list = new ArrayList(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 { 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> 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); } }