Home | History | Annotate | Download | only in oauth
      1 /*
      2  * Copyright 2007 Netflix, Inc.
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *     http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package net.oauth;
     18 
     19 import java.io.ByteArrayOutputStream;
     20 import java.io.IOException;
     21 import java.io.OutputStream;
     22 import java.io.UnsupportedEncodingException;
     23 import java.net.URLDecoder;
     24 import java.net.URLEncoder;
     25 import java.util.ArrayList;
     26 import java.util.HashMap;
     27 import java.util.List;
     28 import java.util.Map;
     29 
     30 /**
     31  * @author John Kristian
     32  * @hide
     33  */
     34 public class OAuth {
     35 
     36     public static final String VERSION_1_0 = "1.0";
     37 
     38     /** The encoding used to represent characters as bytes. */
     39     public static final String ENCODING = "UTF-8";
     40 
     41     /** The MIME type for a sequence of OAuth parameters. */
     42     public static final String FORM_ENCODED = "application/x-www-form-urlencoded";
     43 
     44     public static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key";
     45     public static final String OAUTH_TOKEN = "oauth_token";
     46     public static final String OAUTH_TOKEN_SECRET = "oauth_token_secret";
     47     public static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method";
     48     public static final String OAUTH_SIGNATURE = "oauth_signature";
     49     public static final String OAUTH_TIMESTAMP = "oauth_timestamp";
     50     public static final String OAUTH_NONCE = "oauth_nonce";
     51     public static final String OAUTH_VERSION = "oauth_version";
     52 
     53     public static final String HMAC_SHA1 = "HMAC-SHA1";
     54     public static final String RSA_SHA1 = "RSA-SHA1";
     55 
     56     public static class Problems {
     57         public static final String TOKEN_NOT_AUTHORIZED = "token_not_authorized";
     58         public static final String INVALID_USED_NONCE = "invalid_used_nonce";
     59         public static final String SIGNATURE_INVALID = "signature_invalid";
     60         public static final String INVALID_EXPIRED_TOKEN = "invalid_expired_token";
     61         public static final String INVALID_CONSUMER_KEY = "invalid_consumer_key";
     62         public static final String CONSUMER_KEY_REFUSED = "consumer_key_refused";
     63         public static final String TIMESTAMP_REFUSED = "timestamp_refused";
     64         public static final String PARAMETER_REJECTED = "parameter_rejected";
     65         public static final String PARAMETER_ABSENT = "parameter_absent";
     66         public static final String VERSION_REJECTED = "version_rejected";
     67         public static final String SIGNATURE_METHOD_REJECTED = "signature_method_rejected";
     68 
     69         public static final String OAUTH_PARAMETERS_ABSENT = "oauth_parameters_absent";
     70         public static final String OAUTH_PARAMETERS_REJECTED = "oauth_parameters_rejected";
     71         public static final String OAUTH_ACCEPTABLE_TIMESTAMPS = "oauth_acceptable_timestamps";
     72         public static final String OAUTH_ACCEPTABLE_VERSIONS = "oauth_acceptable_versions";
     73     }
     74 
     75     /** Return true if the given Content-Type header means FORM_ENCODED. */
     76     public static boolean isFormEncoded(String contentType) {
     77         if (contentType == null) {
     78             return false;
     79         }
     80         int semi = contentType.indexOf(";");
     81         if (semi >= 0) {
     82             contentType = contentType.substring(0, semi);
     83         }
     84         return FORM_ENCODED.equalsIgnoreCase(contentType.trim());
     85     }
     86 
     87     /**
     88      * Construct a form-urlencoded document containing the given sequence of
     89      * name/value pairs. Use OAuth percent encoding (not exactly the encoding
     90      * mandated by HTTP).
     91      */
     92     public static String formEncode(Iterable<? extends Map.Entry> parameters)
     93             throws IOException {
     94         ByteArrayOutputStream b = new ByteArrayOutputStream();
     95         formEncode(parameters, b);
     96         return new String(b.toByteArray());
     97     }
     98 
     99     /**
    100      * Write a form-urlencoded document into the given stream, containing the
    101      * given sequence of name/value pairs.
    102      */
    103     public static void formEncode(Iterable<? extends Map.Entry> parameters,
    104             OutputStream into) throws IOException {
    105         if (parameters != null) {
    106             boolean first = true;
    107             for (Map.Entry parameter : parameters) {
    108                 if (first) {
    109                     first = false;
    110                 } else {
    111                     into.write('&');
    112                 }
    113                 into.write(percentEncode(toString(parameter.getKey()))
    114                         .getBytes());
    115                 into.write('=');
    116                 into.write(percentEncode(toString(parameter.getValue()))
    117                         .getBytes());
    118             }
    119         }
    120     }
    121 
    122     /** Parse a form-urlencoded document. */
    123     public static List<Parameter> decodeForm(String form) {
    124         List<Parameter> list = new ArrayList<Parameter>();
    125         if (!isEmpty(form)) {
    126             for (String nvp : form.split("\\&")) {
    127                 int equals = nvp.indexOf('=');
    128                 String name;
    129                 String value;
    130                 if (equals < 0) {
    131                     name = decodePercent(nvp);
    132                     value = null;
    133                 } else {
    134                     name = decodePercent(nvp.substring(0, equals));
    135                     value = decodePercent(nvp.substring(equals + 1));
    136                 }
    137                 list.add(new Parameter(name, value));
    138             }
    139         }
    140         return list;
    141     }
    142 
    143     /** Construct a &-separated list of the given values, percentEncoded. */
    144     public static String percentEncode(Iterable values) {
    145         StringBuilder p = new StringBuilder();
    146         for (Object v : values) {
    147             if (p.length() > 0) {
    148                 p.append("&");
    149             }
    150             p.append(OAuth.percentEncode(toString(v)));
    151         }
    152         return p.toString();
    153     }
    154 
    155     public static String percentEncode(String s) {
    156         if (s == null) {
    157             return "";
    158         }
    159         try {
    160             return URLEncoder.encode(s, ENCODING)
    161                     // OAuth encodes some characters differently:
    162                     .replace("+", "%20").replace("*", "%2A")
    163                     .replace("%7E", "~");
    164             // This could be done faster with more hand-crafted code.
    165         } catch (UnsupportedEncodingException wow) {
    166             throw new RuntimeException(wow.getMessage(), wow);
    167         }
    168     }
    169 
    170     public static String decodePercent(String s) {
    171         try {
    172             return URLDecoder.decode(s, ENCODING);
    173             // This implements http://oauth.pbwiki.com/FlexibleDecoding
    174         } catch (java.io.UnsupportedEncodingException wow) {
    175             throw new RuntimeException(wow.getMessage(), wow);
    176         }
    177     }
    178 
    179     /**
    180      * Construct a Map containing a copy of the given parameters. If several
    181      * parameters have the same name, the Map will contain the first value,
    182      * only.
    183      */
    184     public static Map<String, String> newMap(Iterable<? extends Map.Entry> from) {
    185         Map<String, String> map = new HashMap<String, String>();
    186         if (from != null) {
    187             for (Map.Entry f : from) {
    188                 String key = toString(f.getKey());
    189                 if (!map.containsKey(key)) {
    190                     map.put(key, toString(f.getValue()));
    191                 }
    192             }
    193         }
    194         return map;
    195     }
    196 
    197     /** Construct a list of Parameters from name, value, name, value... */
    198     public static List<Parameter> newList(String... parameters) {
    199         List<Parameter> list = new ArrayList<Parameter>(parameters.length / 2);
    200         for (int p = 0; p + 1 < parameters.length; p += 2) {
    201             list.add(new Parameter(parameters[p], parameters[p + 1]));
    202         }
    203         return list;
    204     }
    205 
    206     /** A name/value pair. */
    207     public static class Parameter implements Map.Entry<String, String> {
    208 
    209         public Parameter(String key, String value) {
    210             this.key = key;
    211             this.value = value;
    212         }
    213 
    214         private final String key;
    215 
    216         private String value;
    217 
    218         public String getKey() {
    219             return key;
    220         }
    221 
    222         public String getValue() {
    223             return value;
    224         }
    225 
    226         public String setValue(String value) {
    227             try {
    228                 return this.value;
    229             } finally {
    230                 this.value = value;
    231             }
    232         }
    233 
    234         @Override
    235         public String toString() {
    236             return percentEncode(getKey()) + '=' + percentEncode(getValue());
    237         }
    238 
    239         @Override
    240         public int hashCode()
    241         {
    242             final int prime = 31;
    243             int result = 1;
    244             result = prime * result + ((key == null) ? 0 : key.hashCode());
    245             result = prime * result + ((value == null) ? 0 : value.hashCode());
    246             return result;
    247         }
    248 
    249         @Override
    250         public boolean equals(Object obj)
    251         {
    252             if (this == obj)
    253                 return true;
    254             if (obj == null)
    255                 return false;
    256             if (getClass() != obj.getClass())
    257                 return false;
    258             final Parameter that = (Parameter) obj;
    259             if (key == null) {
    260                 if (that.key != null)
    261                     return false;
    262             } else if (!key.equals(that.key))
    263                 return false;
    264             if (value == null) {
    265                 if (that.value != null)
    266                     return false;
    267             } else if (!value.equals(that.value))
    268                 return false;
    269             return true;
    270         }
    271     }
    272 
    273     private static final String toString(Object from) {
    274         return (from == null) ? null : from.toString();
    275     }
    276 
    277     /**
    278      * Construct a URL like the given one, but with the given parameters added
    279      * to its query string.
    280      */
    281     public static String addParameters(String url, String... parameters)
    282             throws IOException {
    283         return addParameters(url, newList(parameters));
    284     }
    285 
    286     public static String addParameters(String url,
    287             Iterable<? extends Map.Entry<String, String>> parameters)
    288             throws IOException {
    289         String form = formEncode(parameters);
    290         if (form == null || form.length() <= 0) {
    291             return url;
    292         } else {
    293             return url + ((url.indexOf("?") < 0) ? '?' : '&') + form;
    294         }
    295     }
    296 
    297     public static boolean isEmpty(String str) {
    298 	return (str == null) || (str.length() == 0);
    299     }
    300 }
    301