-
-
Save serj-lotutovici/f85518e99b23008b3180 to your computer and use it in GitHub Desktop.
| /* | |
| * Copyright (C) 2015 Jake Wharton | |
| * | |
| * 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. | |
| */ | |
| import com.squareup.okhttp.HttpUrl; | |
| import com.squareup.okhttp.Interceptor; | |
| import com.squareup.okhttp.Request; | |
| import com.squareup.okhttp.RequestBody; | |
| import com.squareup.okhttp.Response; | |
| import java.io.IOException; | |
| import java.security.InvalidKeyException; | |
| import java.security.NoSuchAlgorithmException; | |
| import java.security.SecureRandom; | |
| import java.util.Map; | |
| import java.util.Random; | |
| import java.util.SortedMap; | |
| import java.util.TreeMap; | |
| import javax.crypto.Mac; | |
| import javax.crypto.spec.SecretKeySpec; | |
| import okio.Buffer; | |
| import okio.ByteString; | |
| public final class Oauth1SigningInterceptor implements Interceptor { | |
| private static final String OAUTH_CONSUMER_KEY = "oauth_consumer_key"; | |
| private static final String OAUTH_NONCE = "oauth_nonce"; | |
| private static final String OAUTH_SIGNATURE = "oauth_signature"; | |
| private static final String OAUTH_SIGNATURE_METHOD = "oauth_signature_method"; | |
| private static final String OAUTH_SIGNATURE_METHOD_VALUE = "HMAC-SHA1"; | |
| private static final String OAUTH_TIMESTAMP = "oauth_timestamp"; | |
| private static final String OAUTH_ACCESS_TOKEN = "oauth_token"; | |
| private static final String OAUTH_VERSION = "oauth_version"; | |
| private static final String OAUTH_VERSION_VALUE = "1.0"; | |
| private final String consumerKey; | |
| private final String consumerSecret; | |
| private final String accessToken; | |
| private final String accessSecret; | |
| private final Random random; | |
| private final Clock clock; | |
| private Oauth1SigningInterceptor(String consumerKey, String consumerSecret, String accessToken, | |
| String accessSecret, Random random, Clock clock) { | |
| this.consumerKey = consumerKey; | |
| this.consumerSecret = consumerSecret; | |
| this.accessToken = accessToken; | |
| this.accessSecret = accessSecret; | |
| this.random = random; | |
| this.clock = clock; | |
| } | |
| @Override public Response intercept(Chain chain) throws IOException { | |
| return chain.proceed(signRequest(chain.request())); | |
| } | |
| public Request signRequest(Request request) throws IOException { | |
| byte[] nonce = new byte[32]; | |
| random.nextBytes(nonce); | |
| String oauthNonce = ByteString.of(nonce).base64().replaceAll("\\W", ""); | |
| String oauthTimestamp = clock.millis(); | |
| String consumerKeyValue = UrlEscapeUtils.escape(consumerKey); | |
| String accessTokenValue = UrlEscapeUtils.escape(accessToken); | |
| SortedMap<String, String> parameters = new TreeMap<>(); | |
| parameters.put(OAUTH_CONSUMER_KEY, consumerKeyValue); | |
| parameters.put(OAUTH_ACCESS_TOKEN, accessTokenValue); | |
| parameters.put(OAUTH_NONCE, oauthNonce); | |
| parameters.put(OAUTH_TIMESTAMP, oauthTimestamp); | |
| parameters.put(OAUTH_SIGNATURE_METHOD, OAUTH_SIGNATURE_METHOD_VALUE); | |
| parameters.put(OAUTH_VERSION, OAUTH_VERSION_VALUE); | |
| HttpUrl url = request.httpUrl(); | |
| for (int i = 0; i < url.querySize(); i++) { | |
| parameters.put(UrlEscapeUtils.escape(url.queryParameterName(i)), | |
| UrlEscapeUtils.escape(url.queryParameterValue(i))); | |
| } | |
| Buffer body = new Buffer(); | |
| RequestBody requestBody = request.body(); | |
| if (requestBody != null) { | |
| requestBody.writeTo(body); | |
| } | |
| while (!body.exhausted()) { | |
| long keyEnd = body.indexOf((byte) '='); | |
| if (keyEnd == -1) throw new IllegalStateException("Key with no value: " + body.readUtf8()); | |
| String key = body.readUtf8(keyEnd); | |
| body.skip(1); // Equals. | |
| long valueEnd = body.indexOf((byte) '&'); | |
| String value = valueEnd == -1 ? body.readUtf8() : body.readUtf8(valueEnd); | |
| if (valueEnd != -1) body.skip(1); // Ampersand. | |
| parameters.put(key, value); | |
| } | |
| Buffer base = new Buffer(); | |
| String method = request.method(); | |
| base.writeUtf8(method); | |
| base.writeByte('&'); | |
| base.writeUtf8( | |
| UrlEscapeUtils.escape(request.httpUrl().newBuilder().query(null).build().toString())); | |
| base.writeByte('&'); | |
| boolean first = true; | |
| for (Map.Entry<String, String> entry : parameters.entrySet()) { | |
| if (!first) base.writeUtf8(UrlEscapeUtils.escape("&")); | |
| first = false; | |
| base.writeUtf8(UrlEscapeUtils.escape(entry.getKey())); | |
| base.writeUtf8(UrlEscapeUtils.escape("=")); | |
| base.writeUtf8(UrlEscapeUtils.escape(entry.getValue())); | |
| } | |
| String signingKey = | |
| UrlEscapeUtils.escape(consumerSecret) + "&" + UrlEscapeUtils.escape(accessSecret); | |
| SecretKeySpec keySpec = new SecretKeySpec(signingKey.getBytes(), "HmacSHA1"); | |
| Mac mac; | |
| try { | |
| mac = Mac.getInstance("HmacSHA1"); | |
| mac.init(keySpec); | |
| } catch (NoSuchAlgorithmException | InvalidKeyException e) { | |
| throw new IllegalStateException(e); | |
| } | |
| byte[] result = mac.doFinal(base.readByteArray()); | |
| String signature = ByteString.of(result).base64(); | |
| String authorization = | |
| "OAuth " + OAUTH_CONSUMER_KEY + "=\"" + consumerKeyValue + "\", " + OAUTH_NONCE + "=\"" | |
| + oauthNonce + "\", " + OAUTH_SIGNATURE + "=\"" + UrlEscapeUtils.escape(signature) | |
| + "\", " + OAUTH_SIGNATURE_METHOD + "=\"" + OAUTH_SIGNATURE_METHOD_VALUE + "\", " | |
| + OAUTH_TIMESTAMP + "=\"" + oauthTimestamp + "\", " + OAUTH_ACCESS_TOKEN + "=\"" | |
| + accessTokenValue + "\", " + OAUTH_VERSION + "=\"" + OAUTH_VERSION_VALUE + "\""; | |
| return request.newBuilder().addHeader("Authorization", authorization).build(); | |
| } | |
| public static final class Builder { | |
| private String consumerKey; | |
| private String consumerSecret; | |
| private String accessToken; | |
| private String accessSecret; | |
| private Random random = new SecureRandom(); | |
| private Clock clock = new Clock(); | |
| public Builder consumerKey(String consumerKey) { | |
| if (consumerKey == null) throw new NullPointerException("consumerKey = null"); | |
| this.consumerKey = consumerKey; | |
| return this; | |
| } | |
| public Builder consumerSecret(String consumerSecret) { | |
| if (consumerSecret == null) throw new NullPointerException("consumerSecret = null"); | |
| this.consumerSecret = consumerSecret; | |
| return this; | |
| } | |
| public Builder accessToken(String accessToken) { | |
| if (accessToken == null) throw new NullPointerException("accessToken == null"); | |
| this.accessToken = accessToken; | |
| return this; | |
| } | |
| public Builder accessSecret(String accessSecret) { | |
| if (accessSecret == null) throw new NullPointerException("accessSecret == null"); | |
| this.accessSecret = accessSecret; | |
| return this; | |
| } | |
| public Builder random(Random random) { | |
| if (random == null) throw new NullPointerException("random == null"); | |
| this.random = random; | |
| return this; | |
| } | |
| public Builder clock(Clock clock) { | |
| if (clock == null) throw new NullPointerException("clock == null"); | |
| this.clock = clock; | |
| return this; | |
| } | |
| public Oauth1SigningInterceptor build() { | |
| if (consumerKey == null) throw new IllegalStateException("consumerKey not set"); | |
| if (consumerSecret == null) throw new IllegalStateException("consumerSecret not set"); | |
| if (accessToken == null) throw new IllegalStateException("accessToken not set"); | |
| if (accessSecret == null) throw new IllegalStateException("accessSecret not set"); | |
| return new Oauth1SigningInterceptor(consumerKey, consumerSecret, accessToken, accessSecret, | |
| random, clock); | |
| } | |
| } | |
| /** Simple clock like class, to allow time mocking. */ | |
| static class Clock { | |
| /** Returns the current time in milliseconds divided by 1K. */ | |
| public String millis() { | |
| return Long.toString(System.currentTimeMillis() / 1000L); | |
| } | |
| } | |
| } |
| /* | |
| * Copyright (C) 2015 Jake Wharton | |
| * | |
| * 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. | |
| */ | |
| import com.squareup.okhttp.FormEncodingBuilder; | |
| import com.squareup.okhttp.Request; | |
| import com.squareup.okhttp.RequestBody; | |
| import java.io.IOException; | |
| import java.util.Random; | |
| import okio.ByteString; | |
| import org.junit.Before; | |
| import org.junit.Test; | |
| import static org.assertj.core.api.Assertions.assertThat; | |
| import static org.mockito.Mockito.mock; | |
| import static org.mockito.Mockito.when; | |
| public final class Oauth1SigningInterceptorTest { | |
| Oauth1SigningInterceptor oauth1; | |
| @Before public void setUp() throws IOException { | |
| // Data from https://dev.twitter.com/oauth/overview/authorizing-requests. | |
| // Tested via http://www.oauth-signatur.de/en | |
| Random notRandom = new Random() { | |
| @Override public void nextBytes(byte[] bytes) { | |
| if (bytes.length != 32) throw new AssertionError(); | |
| ByteString hex = ByteString.decodeBase64("kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4c+g"); | |
| byte[] nonce = hex.toByteArray(); | |
| System.arraycopy(nonce, 0, bytes, 0, nonce.length); | |
| } | |
| }; | |
| // Mock the time :) | |
| Oauth1SigningInterceptor.Clock clock = mock(Oauth1SigningInterceptor.Clock.class); | |
| when(clock.millis()).thenReturn("1318622958"); | |
| oauth1 = new Oauth1SigningInterceptor.Builder() | |
| .consumerKey("xvz1evFS4wEEPTGEFPHBog") | |
| .consumerSecret("kAcSOqF21Fu85e7zjz7ZN2U4ZRhfV3WpwPAoE3Z7kBw") | |
| .accessToken("370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb") | |
| .accessSecret("LswwdoUaIvS8ltyTt5jkRh4J50vUPVVHtR2YPi5kE") | |
| .random(notRandom) | |
| .clock(clock) | |
| .build(); | |
| } | |
| @Test public void withBody() throws IOException { | |
| RequestBody body = new FormEncodingBuilder() // | |
| .add("status", "Hello Ladies + Gentlemen, a signed OAuth request!") | |
| .build(); | |
| Request request = new Request.Builder() // | |
| .url("https://api.twitter.com/1/statuses/update.json?include_entities=true") | |
| .post(body) | |
| .build(); | |
| Request signed = oauth1.signRequest(request); | |
| assertAuthHeader(signed, "tnnArxj06cWHq44gCs1OSKk%2FjLY%3D"); | |
| } | |
| @Test public void noBody() throws IOException { | |
| Request request = new Request.Builder() // | |
| .url("https://api.twitter.com/1.1/statuses/mentions_timeline.json?count=100&include_entities=false") | |
| .build(); | |
| Request signed = oauth1.signRequest(request); | |
| assertAuthHeader(signed, "hn5jxegoQxNM6SXvQgVhK15yQL8%3D"); | |
| } | |
| @Test public void urlSameQueryParams() throws IOException { | |
| Request request = new Request.Builder() // | |
| .url("https://api.twitter.com/1.1/statuses/home_timeline.json?since_id=12&since_id=13") | |
| .build(); | |
| Request signed = oauth1.signRequest(request); | |
| assertAuthHeader(signed, "R8m%2BYY%2FZG5GJ%2F%2F3zCrE65DkTdCk%3D"); | |
| } | |
| /** | |
| * Asserts that the provided request contains an expected header, with provided oauth signature. | |
| */ | |
| private static void assertAuthHeader(Request request, String signature) { | |
| assertThat(request.header("Authorization")).isEqualTo("OAuth " | |
| + "oauth_consumer_key=\"xvz1evFS4wEEPTGEFPHBog\", " | |
| + "oauth_nonce=\"kYjzVBB8Y0ZFabxSWbWovY3uYSQ2pTgmZeNu2VS4cg\", " | |
| + "oauth_signature=\"" + signature + "\", " | |
| + "oauth_signature_method=\"HMAC-SHA1\", " | |
| + "oauth_timestamp=\"1318622958\", " | |
| + "oauth_token=\"370773112-GmHxMAgYyLbNEtIKZeRNFsMKPR9EyMZeS9weJAEb\", " | |
| + "oauth_version=\"1.0\""); | |
| } | |
| } |
| /* | |
| * Copyright (C) 2008 The Guava Authors | |
| * | |
| * 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. | |
| */ | |
| /** | |
| * Utility class that converts literal text into a format safe for inclusion in a url form context. | |
| * | |
| * <p>This is a ripoff of Guava's {@code PercentEscaper, UnicodeEscaper, Escaper}, it represents | |
| * the same behaviour as calling {@code UrlEscapers.urlFormParameterEscaper()}. Since the purpose | |
| * for this class is to avoid adding Guava as a dependency, all generic structure and abstractions | |
| * where removed. | |
| * | |
| * <p>Because, in general, escaping operates on the code points of a string and not on its | |
| * individual {@code char} values, it is not safe to assume that {@code escape(s)} is equivalent to | |
| * {@code escape(s.substring(0, n)) + escape(s.substing(n))} for arbitrary {@code n}. This is | |
| * because of the possibility of splitting a surrogate pair. The only case in which it is safe to | |
| * escape strings and concatenate the results is if you can rule out this possibility, either by | |
| * splitting an existing long string into short strings adaptively around {@linkplain | |
| * Character#isHighSurrogate surrogate} {@linkplain Character#isLowSurrogate pairs}, or by starting | |
| * with short strings already known to be free of unpaired surrogates. | |
| * | |
| * @author David Beaumont | |
| */ | |
| final class UrlEscapeUtils { | |
| /** The amount of padding (chars) to use when growing the escape buffer. */ | |
| private static final int DEST_PAD = 32; | |
| /** Url form parameter safe chars. */ | |
| private static final String SAFE_CHARS = "-_.*"; | |
| /** This escaper represents spaces as '+'. */ | |
| private static final char[] PLUS_SIGN = { '+' }; | |
| /** Percent escapers output upper case hex digits (uri escapers require this). */ | |
| private static final char[] UPPER_HEX_DIGITS = "0123456789ABCDEF".toCharArray(); | |
| /** | |
| * An array of flags where for any {@code char c} if {@code SAFE_OCTETS[c]} is | |
| * true then {@code c} should remain unmodified in the output. If | |
| * {@code c > SAFE_OCTETS.length} then it should be escaped. | |
| */ | |
| private static final boolean[] SAFE_OCTETS = createSafeOctets( | |
| SAFE_CHARS + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "0123456789"); | |
| /** | |
| * Returns the escaped form of a given literal string. | |
| * | |
| * @param string the literal string to be escaped | |
| * @return the escaped form of {@code string} | |
| * @throws NullPointerException if {@code string} is null | |
| * @throws IllegalArgumentException if {@code string} contains badly formed UTF-16 or cannot be | |
| * escaped for any other reason | |
| */ | |
| public static String escape(String string) { | |
| checkNotNull(string, "string == null"); | |
| int slen = string.length(); | |
| for (int index = 0; index < slen; index++) { | |
| char c = string.charAt(index); | |
| if (c >= SAFE_OCTETS.length || !SAFE_OCTETS[c]) { | |
| return escapeSlow(string, index); | |
| } | |
| } | |
| return string; | |
| } | |
| /** | |
| * Returns the escaped form of a given literal string, starting at the given index. | |
| * | |
| * <p>This method is not reentrant and may only be invoked by the top level | |
| * {@link #escape(String)} method. | |
| * | |
| * @param s the literal string to be escaped | |
| * @param index the index to start escaping from | |
| * @return the escaped form of {@code string} | |
| * @throws NullPointerException if {@code string} is null | |
| * @throws IllegalArgumentException if invalid surrogate characters are | |
| * encountered | |
| */ | |
| private static String escapeSlow(String s, int index) { | |
| int end = s.length(); | |
| // Get a destination buffer and setup some loop variables. | |
| char[] dest = new char[1024]; | |
| int destIndex = 0; | |
| int unescapedChunkStart = 0; | |
| while (index < end) { | |
| int cp = codePointAt(s, index, end); | |
| if (cp < 0) { | |
| throw new IllegalArgumentException("Trailing high surrogate at end of input"); | |
| } | |
| // It is possible for this to return null because nextEscapeIndex() may | |
| // (for performance reasons) yield some false positives but it must never | |
| // give false negatives. | |
| char[] escaped = escape(cp); | |
| int nextIndex = index + (Character.isSupplementaryCodePoint(cp) ? 2 : 1); | |
| if (escaped != null) { | |
| int charsSkipped = index - unescapedChunkStart; | |
| // This is the size needed to add the replacement, not the full | |
| // size needed by the string. We only regrow when we absolutely must. | |
| int sizeNeeded = destIndex + charsSkipped + escaped.length; | |
| if (dest.length < sizeNeeded) { | |
| int destLength = sizeNeeded + (end - index) + DEST_PAD; | |
| dest = growBuffer(dest, destIndex, destLength); | |
| } | |
| // If we have skipped any characters, we need to copy them now. | |
| if (charsSkipped > 0) { | |
| s.getChars(unescapedChunkStart, index, dest, destIndex); | |
| destIndex += charsSkipped; | |
| } | |
| if (escaped.length > 0) { | |
| System.arraycopy(escaped, 0, dest, destIndex, escaped.length); | |
| destIndex += escaped.length; | |
| } | |
| // If we dealt with an escaped character, reset the unescaped range. | |
| unescapedChunkStart = nextIndex; | |
| } | |
| index = nextEscapeIndex(s, nextIndex, end); | |
| } | |
| // Process trailing unescaped characters - no need to account for escaped | |
| // length or padding the allocation. | |
| int charsSkipped = end - unescapedChunkStart; | |
| if (charsSkipped > 0) { | |
| int endIndex = destIndex + charsSkipped; | |
| if (dest.length < endIndex) { | |
| dest = growBuffer(dest, destIndex, endIndex); | |
| } | |
| s.getChars(unescapedChunkStart, end, dest, destIndex); | |
| destIndex = endIndex; | |
| } | |
| return new String(dest, 0, destIndex); | |
| } | |
| /** Escapes the given Unicode code point in UTF-8. */ | |
| static char[] escape(int cp) { | |
| // We should never get negative values here but if we do it will throw an | |
| // IndexOutOfBoundsException, so at least it will get spotted. | |
| if (cp < SAFE_OCTETS.length && SAFE_OCTETS[cp]) { | |
| return null; | |
| } else if (cp == ' ') { | |
| return PLUS_SIGN; | |
| } else if (cp <= 0x7F) { | |
| // Single byte UTF-8 characters | |
| // Start with "%--" and fill in the blanks | |
| char[] dest = new char[3]; | |
| dest[0] = '%'; | |
| dest[2] = UPPER_HEX_DIGITS[cp & 0xF]; | |
| dest[1] = UPPER_HEX_DIGITS[cp >>> 4]; | |
| return dest; | |
| } else if (cp <= 0x7ff) { | |
| // Two byte UTF-8 characters [cp >= 0x80 && cp <= 0x7ff] | |
| // Start with "%--%--" and fill in the blanks | |
| char[] dest = new char[6]; | |
| dest[0] = '%'; | |
| dest[3] = '%'; | |
| dest[5] = UPPER_HEX_DIGITS[cp & 0xF]; | |
| cp >>>= 4; | |
| dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; | |
| cp >>>= 2; | |
| dest[2] = UPPER_HEX_DIGITS[cp & 0xF]; | |
| cp >>>= 4; | |
| dest[1] = UPPER_HEX_DIGITS[0xC | cp]; | |
| return dest; | |
| } else if (cp <= 0xffff) { | |
| // Three byte UTF-8 characters [cp >= 0x800 && cp <= 0xffff] | |
| // Start with "%E-%--%--" and fill in the blanks | |
| char[] dest = new char[9]; | |
| dest[0] = '%'; | |
| dest[1] = 'E'; | |
| dest[3] = '%'; | |
| dest[6] = '%'; | |
| dest[8] = UPPER_HEX_DIGITS[cp & 0xF]; | |
| cp >>>= 4; | |
| dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; | |
| cp >>>= 2; | |
| dest[5] = UPPER_HEX_DIGITS[cp & 0xF]; | |
| cp >>>= 4; | |
| dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; | |
| cp >>>= 2; | |
| dest[2] = UPPER_HEX_DIGITS[cp]; | |
| return dest; | |
| } else if (cp <= 0x10ffff) { | |
| char[] dest = new char[12]; | |
| // Four byte UTF-8 characters [cp >= 0xffff && cp <= 0x10ffff] | |
| // Start with "%F-%--%--%--" and fill in the blanks | |
| dest[0] = '%'; | |
| dest[1] = 'F'; | |
| dest[3] = '%'; | |
| dest[6] = '%'; | |
| dest[9] = '%'; | |
| dest[11] = UPPER_HEX_DIGITS[cp & 0xF]; | |
| cp >>>= 4; | |
| dest[10] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; | |
| cp >>>= 2; | |
| dest[8] = UPPER_HEX_DIGITS[cp & 0xF]; | |
| cp >>>= 4; | |
| dest[7] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; | |
| cp >>>= 2; | |
| dest[5] = UPPER_HEX_DIGITS[cp & 0xF]; | |
| cp >>>= 4; | |
| dest[4] = UPPER_HEX_DIGITS[0x8 | (cp & 0x3)]; | |
| cp >>>= 2; | |
| dest[2] = UPPER_HEX_DIGITS[cp & 0x7]; | |
| return dest; | |
| } else { | |
| // If this ever happens it is due to bug in UnicodeEscaper, not bad input. | |
| throw new IllegalArgumentException("Invalid unicode character value " + cp); | |
| } | |
| } | |
| /** | |
| * Creates a boolean array with entries corresponding to the character values | |
| * specified in safeChars set to true. The array is as small as is required to | |
| * hold the given character information. | |
| */ | |
| private static boolean[] createSafeOctets(String safeChars) { | |
| int maxChar = -1; | |
| char[] safeCharArray = safeChars.toCharArray(); | |
| for (char c : safeCharArray) { | |
| maxChar = Math.max(c, maxChar); | |
| } | |
| boolean[] octets = new boolean[maxChar + 1]; | |
| for (char c : safeCharArray) { | |
| octets[c] = true; | |
| } | |
| return octets; | |
| } | |
| /** | |
| * Scans a sub-sequence of characters from a given {@link CharSequence}, | |
| * returning the index of the next character that requires escaping. | |
| * | |
| * @param csq a sequence of characters | |
| * @param start the index of the first character to be scanned | |
| * @param end the index immediately after the last character to be scanned | |
| * @throws IllegalArgumentException if the scanned sub-sequence of {@code csq} | |
| * contains invalid surrogate pairs | |
| */ | |
| private static int nextEscapeIndex(CharSequence csq, int start, int end) { | |
| checkNotNull(csq, "csq == null"); | |
| for (; start < end; start++) { | |
| char c = csq.charAt(start); | |
| if (c >= SAFE_OCTETS.length || !SAFE_OCTETS[c]) { | |
| break; | |
| } | |
| } | |
| return start; | |
| } | |
| /** | |
| * Returns the Unicode code point of the character at the given index. | |
| * | |
| * <p>Unlike {@link Character#codePointAt(CharSequence, int)} or | |
| * {@link String#codePointAt(int)} this method will never fail silently when | |
| * encountering an invalid surrogate pair. | |
| * | |
| * <p>The behaviour of this method is as follows: | |
| * <ol> | |
| * <li>If {@code index >= end}, {@link IndexOutOfBoundsException} is thrown. | |
| * <li><b>If the character at the specified index is not a surrogate, it is | |
| * returned.</b> | |
| * <li>If the first character was a high surrogate value, then an attempt is | |
| * made to read the next character. | |
| * <ol> | |
| * <li><b>If the end of the sequence was reached, the negated value of | |
| * the trailing high surrogate is returned.</b> | |
| * <li><b>If the next character was a valid low surrogate, the code point | |
| * value of the high/low surrogate pair is returned.</b> | |
| * <li>If the next character was not a low surrogate value, then | |
| * {@link IllegalArgumentException} is thrown. | |
| * </ol> | |
| * <li>If the first character was a low surrogate value, | |
| * {@link IllegalArgumentException} is thrown. | |
| * </ol> | |
| * | |
| * @param seq the sequence of characters from which to decode the code point | |
| * @param index the index of the first character to decode | |
| * @param end the index beyond the last valid character to decode | |
| * @return the Unicode code point for the given index or the negated value of | |
| * the trailing high surrogate character at the end of the sequence | |
| */ | |
| private static int codePointAt(CharSequence seq, int index, int end) { | |
| checkNotNull(seq, "seq == null"); | |
| if (index < end) { | |
| char c1 = seq.charAt(index++); | |
| if (c1 < Character.MIN_HIGH_SURROGATE || c1 > Character.MAX_LOW_SURROGATE) { | |
| // Fast path (first test is probably all we need to do) | |
| return c1; | |
| } else if (c1 <= Character.MAX_HIGH_SURROGATE) { | |
| // If the high surrogate was the last character, return its inverse | |
| if (index == end) { | |
| return -c1; | |
| } | |
| // Otherwise look for the low surrogate following it | |
| char c2 = seq.charAt(index); | |
| if (Character.isLowSurrogate(c2)) { | |
| return Character.toCodePoint(c1, c2); | |
| } | |
| throw new IllegalArgumentException("Expected low surrogate but got char '" | |
| + c2 + "' with value " + (int) c2 + " at index " + index | |
| + " in '" + seq + "'"); | |
| } else { | |
| throw new IllegalArgumentException("Unexpected low surrogate character '" + c1 | |
| + "' with value " + (int) c1 + " at index " + (index - 1) | |
| + " in '" + seq + "'"); | |
| } | |
| } | |
| throw new IndexOutOfBoundsException("Index exceeds specified range"); | |
| } | |
| /** | |
| * Helper method to grow the character buffer as needed, this only happens | |
| * once in a while so it's ok if it's in a method call. If the index passed | |
| * in is 0 then no copying will be done. | |
| */ | |
| private static char[] growBuffer(char[] dest, int index, int size) { | |
| char[] copy = new char[size]; | |
| if (index > 0) { | |
| System.arraycopy(dest, 0, copy, 0, index); | |
| } | |
| return copy; | |
| } | |
| public static <T> T checkNotNull(T reference, String errorMessage) { | |
| if (reference == null) { | |
| throw new NullPointerException(errorMessage); | |
| } | |
| return reference; | |
| } | |
| /** No instances. */ | |
| private UrlEscapeUtils() { | |
| } | |
| } |
| /* | |
| * Copyright (C) 2009 The Guava Authors | |
| * | |
| * 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. | |
| */ | |
| import org.junit.Test; | |
| import static org.junit.Assert.assertEquals; | |
| import static org.junit.Assert.assertNotNull; | |
| import static org.junit.Assert.assertNull; | |
| import static org.junit.Assert.fail; | |
| /** | |
| * Tests for the {@link UrlEscapeUtils} class. | |
| * | |
| * <b>Note: </b> This is a ripoff of Guava's actual {@code UtlEscapersTest}. | |
| * | |
| * @author David Beaumont | |
| * @author Sven Mawson | |
| */ | |
| public class UrlEscapeUtilsTest { | |
| @Test public void actsAsUrlFormParameterEscaper() { | |
| try { | |
| UrlEscapeUtils.escape(null); | |
| fail("Escaping null string should throw exception"); | |
| } catch (NullPointerException x) { | |
| // pass | |
| } | |
| // 0-9, A-Z, a-z should be left unescaped | |
| assertUnescaped('a'); | |
| assertUnescaped('z'); | |
| assertUnescaped('A'); | |
| assertUnescaped('Z'); | |
| assertUnescaped('0'); | |
| assertUnescaped('9'); | |
| // Unreserved characters used in java.net.URLEncoder | |
| assertUnescaped('-'); | |
| assertUnescaped('_'); | |
| assertUnescaped('.'); | |
| assertUnescaped('*'); | |
| assertEscaping("%3D", '='); | |
| assertEscaping("%00", '\u0000'); // nul | |
| assertEscaping("%7F", '\u007f'); // del | |
| assertEscaping("%C2%80", '\u0080'); // xx-00010,x-000000 | |
| assertEscaping("%DF%BF", '\u07ff'); // xx-11111,x-111111 | |
| assertEscaping("%E0%A0%80", '\u0800'); // xxx-0000,x-100000,x-00,0000 | |
| assertEscaping("%EF%BF%BF", '\uffff'); // xxx-1111,x-111111,x-11,1111 | |
| assertUnicodeEscaping("%F0%90%80%80", '\uD800', '\uDC00'); | |
| assertUnicodeEscaping("%F4%8F%BF%BF", '\uDBFF', '\uDFFF'); | |
| assertEquals("", UrlEscapeUtils.escape("")); | |
| assertEquals("safestring", UrlEscapeUtils.escape("safestring")); | |
| assertEquals("embedded%00null", UrlEscapeUtils.escape("embedded\0null")); | |
| assertEquals("max%EF%BF%BFchar", UrlEscapeUtils.escape("max\uffffchar")); | |
| // Specified as safe by RFC 2396 but not by java.net.URLEncoder. | |
| assertEscaping("%21", '!'); | |
| assertEscaping("%28", '('); | |
| assertEscaping("%29", ')'); | |
| assertEscaping("%7E", '~'); | |
| assertEscaping("%27", '\''); | |
| // Plus for spaces | |
| assertEscaping("+", ' '); | |
| assertEscaping("%2B", '+'); | |
| assertEquals("safe+with+spaces", UrlEscapeUtils.escape("safe with spaces")); | |
| assertEquals("foo%40bar.com", UrlEscapeUtils.escape("foo@bar.com")); | |
| } | |
| /** | |
| * Asserts that {@link UrlEscapeUtils} escapes the given character. | |
| * | |
| * @param expected the expected escape result | |
| * @param c the character to test | |
| */ | |
| private static void assertEscaping(String expected, char c) { | |
| String escaped = computeReplacement(c); | |
| assertNotNull(escaped); | |
| assertEquals(expected, escaped); | |
| } | |
| /** | |
| * Asserts that {@link UrlEscapeUtils} does not escape the given character. | |
| * | |
| * @param c the character to test | |
| */ | |
| private static void assertUnescaped(char c) { | |
| assertNull(computeReplacement(c)); | |
| } | |
| /** | |
| * Asserts that {@link UrlEscapeUtils} escapes the given hi/lo surrogate pair into | |
| * the expected string. | |
| * | |
| * @param expected the expected output string | |
| * @param hi the high surrogate pair character | |
| * @param lo the low surrogate pair character | |
| */ | |
| private static void assertUnicodeEscaping(String expected, char hi, char lo) { | |
| int cp = Character.toCodePoint(hi, lo); | |
| String escaped = computeReplacement(cp); | |
| assertNotNull(escaped); | |
| assertEquals(expected, escaped); | |
| } | |
| private static String computeReplacement(char c) { | |
| return stringOrNull(UrlEscapeUtils.escape(c)); | |
| } | |
| private static String computeReplacement(int cp) { | |
| return stringOrNull(UrlEscapeUtils.escape(cp)); | |
| } | |
| private static String stringOrNull(char[] chars) { | |
| return (chars == null) ? null : new String(chars); | |
| } | |
| } |
@Anthonyeef yup... you just have to set the interceptor to the OkHttpClient... something like this:
OkHttpClient client = new OkHttpClient();
client.networkInterceptors().add(oauth1SigningInterceptor);and set that client to your Retrofit instance.
Thank you : )
First of all, thanks for this nice code, but I have a small problem there
inside Oauth1SigningInterceptor class
in parameters.put(key, value);
I have to pass a json object as a body parameter to the request, but after the debugging I see that the key here equals to my json body and the value are equals to ""}}"
do you have any idea why acts like this ?
How can I create Oauth1SigningInterceptor object? in OAuth 1.0a “one-legged” authentication I have only consumer_key and consumer_secret but the constructor also require accessToken and accessSecret
Hey, Thanks for the job ! I only have one problem, when one of my query parameter contains a space, it seems that it is not signing correctly the request (i'm consuming Twitter API). Do you have any idea ?
I have the same question as Zoha131 posted. How do I use Oauth1SigningInterceptor, when only have consumer_key and consumer_secret alone? The REST API publisher has not shared any thing else apart from consumer_key & consumer_secret values. I believe as per 2-legged we only can use "consumer_key & consumer_secret" and 3-legged has more. Please clarify that will be helpful to use this as my SigningInterceptor.
Thank you for making this! And I wonder how to use this code in Android project? It should be fit into Retrofit + OkHttp, right?