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