fixed crashes
This commit is contained in:
parent
dc8d7bd891
commit
9593365fa5
|
@ -351,7 +351,7 @@ public class MicroBlogAPIFactory implements TwidereConstants {
|
|||
}
|
||||
|
||||
@NonNull
|
||||
static String substituteLegacyApiBaseUrl(@NonNull String format, String domain) {
|
||||
static String substituteLegacyApiBaseUrl(@NonNull String format, @Nullable String domain) {
|
||||
final int idxOfSlash = format.indexOf("://");
|
||||
// Not an url
|
||||
if (idxOfSlash < 0) return format;
|
||||
|
@ -362,8 +362,12 @@ public class MicroBlogAPIFactory implements TwidereConstants {
|
|||
if (!host.equalsIgnoreCase("api.twitter.com")) return format;
|
||||
final StringBuilder sb = new StringBuilder();
|
||||
sb.append(format.substring(0, startOfHost));
|
||||
sb.append(domain);
|
||||
sb.append(".twitter.com");
|
||||
if (domain != null) {
|
||||
sb.append(domain);
|
||||
sb.append(".twitter.com");
|
||||
} else {
|
||||
sb.append("twitter.com");
|
||||
}
|
||||
if (endOfHost != -1) {
|
||||
sb.append(format.substring(endOfHost));
|
||||
}
|
||||
|
|
|
@ -1,559 +0,0 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util;
|
||||
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import org.attoparser.AttoParseException;
|
||||
import org.attoparser.IAttoHandler;
|
||||
import org.attoparser.IAttoParser;
|
||||
import org.attoparser.markup.MarkupAttoParser;
|
||||
import org.attoparser.markup.html.AbstractStandardNonValidatingHtmlAttoHandler;
|
||||
import org.attoparser.markup.html.HtmlParsingConfiguration;
|
||||
import org.attoparser.markup.html.elements.IHtmlElement;
|
||||
import org.mariotaku.microblog.library.MicroBlogException;
|
||||
import org.mariotaku.microblog.library.twitter.TwitterOAuth;
|
||||
import org.mariotaku.restfu.RestAPIFactory;
|
||||
import org.mariotaku.restfu.RestClient;
|
||||
import org.mariotaku.restfu.annotation.method.GET;
|
||||
import org.mariotaku.restfu.annotation.method.POST;
|
||||
import org.mariotaku.restfu.http.Endpoint;
|
||||
import org.mariotaku.restfu.http.HttpRequest;
|
||||
import org.mariotaku.restfu.http.HttpResponse;
|
||||
import org.mariotaku.restfu.http.MultiValueMap;
|
||||
import org.mariotaku.restfu.http.RestHttpClient;
|
||||
import org.mariotaku.restfu.http.mime.FormBody;
|
||||
import org.mariotaku.restfu.http.mime.SimpleBody;
|
||||
import org.mariotaku.restfu.oauth.OAuthToken;
|
||||
import org.mariotaku.restfu.okhttp3.OkHttpRestClient;
|
||||
import org.mariotaku.twidere.util.net.SimpleCookieJar;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Response;
|
||||
|
||||
import static org.mariotaku.twidere.TwidereConstants.OAUTH_CALLBACK_OOB;
|
||||
|
||||
public class OAuthPasswordAuthenticator {
|
||||
|
||||
private static final IAttoParser PARSER = new MarkupAttoParser();
|
||||
|
||||
private final TwitterOAuth oauth;
|
||||
private final RestHttpClient client;
|
||||
private final Endpoint endpoint;
|
||||
private final LoginVerificationCallback loginVerificationCallback;
|
||||
private final String userAgent;
|
||||
|
||||
public OAuthPasswordAuthenticator(final TwitterOAuth oauth,
|
||||
final LoginVerificationCallback loginVerificationCallback,
|
||||
final String userAgent) {
|
||||
final RestClient restClient = RestAPIFactory.getRestClient(oauth);
|
||||
this.oauth = oauth;
|
||||
this.endpoint = restClient.getEndpoint();
|
||||
this.loginVerificationCallback = loginVerificationCallback;
|
||||
this.userAgent = userAgent;
|
||||
|
||||
final OkHttpClient oldClient = ((OkHttpRestClient) restClient.getRestClient()).getClient();
|
||||
final OkHttpClient.Builder builder = oldClient.newBuilder();
|
||||
builder.cookieJar(new SimpleCookieJar());
|
||||
builder.addNetworkInterceptor(new EndpointInterceptor(endpoint));
|
||||
this.client = new OkHttpRestClient(builder.build());
|
||||
}
|
||||
|
||||
public OAuthToken getOAuthAccessToken(final String username, final String password) throws AuthenticationException {
|
||||
final OAuthToken requestToken;
|
||||
try {
|
||||
requestToken = oauth.getRequestToken(OAUTH_CALLBACK_OOB);
|
||||
} catch (final MicroBlogException e) {
|
||||
if (e.isCausedByNetworkIssue()) throw new AuthenticationException(e);
|
||||
throw new AuthenticityTokenException(e);
|
||||
}
|
||||
try {
|
||||
final AuthorizeRequestData authorizeRequestData = getAuthorizeRequestData(requestToken);
|
||||
AuthorizeResponseData authorizeResponseData = getAuthorizeResponseData(requestToken,
|
||||
authorizeRequestData, username, password);
|
||||
if (!TextUtils.isEmpty(authorizeResponseData.oauthPin)) {
|
||||
// Here we got OAuth PIN, just get access token directly
|
||||
return oauth.getAccessToken(requestToken, authorizeResponseData.oauthPin);
|
||||
} else if (authorizeResponseData.challenge == null) {
|
||||
// No OAuth pin, or verification challenge, so treat as wrong password
|
||||
throw new WrongUserPassException();
|
||||
}
|
||||
// Go to password verification flow
|
||||
final String challengeType = authorizeResponseData.challenge.challengeType;
|
||||
final String loginVerification = loginVerificationCallback.getLoginVerification(challengeType);
|
||||
final AuthorizeRequestData verificationData = getVerificationData(authorizeResponseData,
|
||||
loginVerification);
|
||||
authorizeResponseData = getAuthorizeResponseData(requestToken,
|
||||
verificationData, username, password);
|
||||
if (TextUtils.isEmpty(authorizeResponseData.oauthPin)) {
|
||||
throw new LoginVerificationException();
|
||||
}
|
||||
return oauth.getAccessToken(requestToken, authorizeResponseData.oauthPin);
|
||||
} catch (final IOException | NullPointerException | MicroBlogException e) {
|
||||
throw new AuthenticationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private AuthorizeRequestData getVerificationData(AuthorizeResponseData authorizeResponseData,
|
||||
@Nullable String challengeResponse) throws IOException, LoginVerificationException {
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
final AuthorizeRequestData data = new AuthorizeRequestData();
|
||||
final MultiValueMap<String> params = new MultiValueMap<>();
|
||||
final AuthorizeResponseData.Verification verification = authorizeResponseData.challenge;
|
||||
params.add("authenticity_token", verification.authenticityToken);
|
||||
params.add("user_id", verification.userId);
|
||||
params.add("challenge_id", verification.challengeId);
|
||||
params.add("challenge_type", verification.challengeType);
|
||||
params.add("platform", verification.platform);
|
||||
params.add("redirect_after_login", verification.redirectAfterLogin);
|
||||
final MultiValueMap<String> requestHeaders = new MultiValueMap<>();
|
||||
requestHeaders.add("User-Agent", userAgent);
|
||||
|
||||
if (!TextUtils.isEmpty(challengeResponse)) {
|
||||
params.add("challenge_response", challengeResponse);
|
||||
}
|
||||
final FormBody authorizationResultBody = new FormBody(params);
|
||||
|
||||
final HttpRequest.Builder authorizeResultBuilder = new HttpRequest.Builder();
|
||||
authorizeResultBuilder.method(POST.METHOD);
|
||||
authorizeResultBuilder.url(endpoint.construct("/account/login_verification"));
|
||||
authorizeResultBuilder.headers(requestHeaders);
|
||||
authorizeResultBuilder.body(authorizationResultBody);
|
||||
response = client.newCall(authorizeResultBuilder.build()).execute();
|
||||
parseAuthorizeRequestData(response, data);
|
||||
if (TextUtils.isEmpty(data.authenticityToken)) {
|
||||
throw new LoginVerificationException();
|
||||
}
|
||||
return data;
|
||||
} catch (AttoParseException e) {
|
||||
throw new LoginVerificationException("Login verification challenge failed", e);
|
||||
} finally {
|
||||
Utils.closeSilently(response);
|
||||
}
|
||||
}
|
||||
|
||||
private void parseAuthorizeRequestData(HttpResponse response, final AuthorizeRequestData data) throws AttoParseException, IOException {
|
||||
final HtmlParsingConfiguration conf = new HtmlParsingConfiguration();
|
||||
final IAttoHandler handler = new AbstractStandardNonValidatingHtmlAttoHandler(conf) {
|
||||
boolean isOAuthFormOpened;
|
||||
|
||||
@Override
|
||||
public void handleHtmlStandaloneElement(IHtmlElement element, boolean minimized,
|
||||
String elementName, Map<String, String> attributes,
|
||||
int line, int col) {
|
||||
handleHtmlOpenElement(element, elementName, attributes, line, col);
|
||||
handleHtmlCloseElement(element, elementName, line, col);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlOpenElement(IHtmlElement element, String elementName,
|
||||
Map<String, String> attributes, int line, int col) {
|
||||
switch (elementName) {
|
||||
case "form": {
|
||||
if (attributes != null && "oauth_form".equals(attributes.get("id"))) {
|
||||
isOAuthFormOpened = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "input": {
|
||||
if (isOAuthFormOpened && attributes != null) {
|
||||
final String name = attributes.get("name");
|
||||
if (TextUtils.isEmpty(name)) break;
|
||||
final String value = attributes.get("value");
|
||||
if (name.equals("authenticity_token")) {
|
||||
data.authenticityToken = value;
|
||||
} else if (name.equals("redirect_after_login")) {
|
||||
data.redirectAfterLogin = value;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlCloseElement(IHtmlElement element, String elementName, int line, int col) {
|
||||
if ("form".equals(elementName)) {
|
||||
isOAuthFormOpened = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
PARSER.parse(SimpleBody.reader(response.getBody()), handler);
|
||||
}
|
||||
|
||||
private AuthorizeResponseData getAuthorizeResponseData(OAuthToken requestToken,
|
||||
AuthorizeRequestData authorizeRequestData,
|
||||
String username, String password) throws IOException, AuthenticationException {
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
final AuthorizeResponseData data = new AuthorizeResponseData();
|
||||
final MultiValueMap<String> params = new MultiValueMap<>();
|
||||
params.add("oauth_token", requestToken.getOauthToken());
|
||||
params.add("authenticity_token", authorizeRequestData.authenticityToken);
|
||||
params.add("redirect_after_login", authorizeRequestData.redirectAfterLogin);
|
||||
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
|
||||
params.add("session[username_or_email]", username);
|
||||
params.add("session[password]", password);
|
||||
}
|
||||
final FormBody authorizationResultBody = new FormBody(params);
|
||||
final MultiValueMap<String> requestHeaders = new MultiValueMap<>();
|
||||
requestHeaders.add("User-Agent", userAgent);
|
||||
data.referer = authorizeRequestData.referer;
|
||||
|
||||
final HttpRequest.Builder authorizeResultBuilder = new HttpRequest.Builder();
|
||||
authorizeResultBuilder.method(POST.METHOD);
|
||||
authorizeResultBuilder.url(endpoint.construct("/oauth/authorize"));
|
||||
authorizeResultBuilder.headers(requestHeaders);
|
||||
authorizeResultBuilder.body(authorizationResultBody);
|
||||
response = client.newCall(authorizeResultBuilder.build()).execute();
|
||||
final HtmlParsingConfiguration conf = new HtmlParsingConfiguration();
|
||||
final IAttoHandler handler = new AbstractStandardNonValidatingHtmlAttoHandler(conf) {
|
||||
boolean isOAuthPinDivOpened;
|
||||
boolean isChallengeFormOpened;
|
||||
|
||||
@Override
|
||||
public void handleHtmlStandaloneElement(IHtmlElement element, boolean minimized,
|
||||
String elementName, Map<String, String> attributes,
|
||||
int line, int col) {
|
||||
handleHtmlOpenElement(element, elementName, attributes, line, col);
|
||||
handleHtmlCloseElement(element, elementName, line, col);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlCloseElement(IHtmlElement element, String elementName, int line, int col) {
|
||||
switch (elementName) {
|
||||
case "div": {
|
||||
isOAuthPinDivOpened = false;
|
||||
break;
|
||||
}
|
||||
case "form": {
|
||||
isChallengeFormOpened = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlOpenElement(IHtmlElement element, String elementName,
|
||||
Map<String, String> attributes, int line, int col) {
|
||||
switch (elementName) {
|
||||
case "div": {
|
||||
if (attributes == null) break;
|
||||
if ("oauth_pin".equals(attributes.get("id"))) {
|
||||
isOAuthPinDivOpened = true;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "form": {
|
||||
if (attributes == null) break;
|
||||
final String id = attributes.get("id");
|
||||
if (id == null) break;
|
||||
switch (id) {
|
||||
case "login-verification-form":
|
||||
case "login-challenge-form": {
|
||||
isChallengeFormOpened = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "input":
|
||||
if (attributes == null) break;
|
||||
if (isChallengeFormOpened) {
|
||||
final String name = attributes.get("name");
|
||||
if (TextUtils.isEmpty(name)) break;
|
||||
final String value = attributes.get("value");
|
||||
switch (name) {
|
||||
case "authenticity_token": {
|
||||
ensureVerification();
|
||||
data.challenge.authenticityToken = value;
|
||||
break;
|
||||
}
|
||||
case "challenge_id": {
|
||||
ensureVerification();
|
||||
data.challenge.challengeId = value;
|
||||
break;
|
||||
}
|
||||
case "challenge_type": {
|
||||
ensureVerification();
|
||||
data.challenge.challengeType = value;
|
||||
break;
|
||||
}
|
||||
case "platform": {
|
||||
ensureVerification();
|
||||
data.challenge.platform = value;
|
||||
break;
|
||||
}
|
||||
case "user_id": {
|
||||
ensureVerification();
|
||||
data.challenge.userId = value;
|
||||
break;
|
||||
}
|
||||
case "redirect_after_login": {
|
||||
ensureVerification();
|
||||
data.challenge.redirectAfterLogin = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureVerification() {
|
||||
if (data.challenge == null) {
|
||||
data.challenge = new AuthorizeResponseData.Verification();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleText(char[] buffer, int offset, int len, int line, int col) throws AttoParseException {
|
||||
if (isOAuthPinDivOpened) {
|
||||
final String s = new String(buffer, offset, len);
|
||||
if (TextUtils.isDigitsOnly(s)) {
|
||||
data.oauthPin = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
PARSER.parse(SimpleBody.reader(response.getBody()), handler);
|
||||
return data;
|
||||
} catch (AttoParseException e) {
|
||||
throw new AuthenticationException("Malformed HTML", e);
|
||||
} finally {
|
||||
Utils.closeSilently(response);
|
||||
}
|
||||
}
|
||||
|
||||
private AuthorizeRequestData getAuthorizeRequestData(OAuthToken requestToken) throws IOException,
|
||||
AuthenticationException {
|
||||
HttpResponse response = null;
|
||||
try {
|
||||
final AuthorizeRequestData data = new AuthorizeRequestData();
|
||||
final HttpRequest.Builder authorizePageBuilder = new HttpRequest.Builder();
|
||||
authorizePageBuilder.method(GET.METHOD);
|
||||
authorizePageBuilder.url(endpoint.construct("/oauth/authorize", new String[]{"oauth_token",
|
||||
requestToken.getOauthToken()}));
|
||||
data.referer = Endpoint.constructUrl("https://api.twitter.com/oauth/authorize",
|
||||
new String[]{"oauth_token", requestToken.getOauthToken()});
|
||||
final MultiValueMap<String> requestHeaders = new MultiValueMap<>();
|
||||
requestHeaders.add("User-Agent", userAgent);
|
||||
authorizePageBuilder.headers(requestHeaders);
|
||||
final HttpRequest authorizePageRequest = authorizePageBuilder.build();
|
||||
response = client.newCall(authorizePageRequest).execute();
|
||||
parseAuthorizeRequestData(response, data);
|
||||
if (TextUtils.isEmpty(data.authenticityToken)) {
|
||||
throw new AuthenticationException();
|
||||
}
|
||||
return data;
|
||||
} catch (AttoParseException e) {
|
||||
throw new AuthenticationException("Malformed HTML", e);
|
||||
} finally {
|
||||
Utils.closeSilently(response);
|
||||
}
|
||||
}
|
||||
|
||||
public static void readOAuthPINFromHtml(Reader reader, final OAuthPinData data) throws AttoParseException, IOException {
|
||||
final HtmlParsingConfiguration conf = new HtmlParsingConfiguration();
|
||||
final IAttoHandler handler = new AbstractStandardNonValidatingHtmlAttoHandler(conf) {
|
||||
boolean isOAuthPinDivOpened;
|
||||
|
||||
@Override
|
||||
public void handleHtmlStandaloneElement(IHtmlElement element, boolean minimized,
|
||||
String elementName, Map<String, String> attributes,
|
||||
int line, int col) {
|
||||
handleHtmlOpenElement(element, elementName, attributes, line, col);
|
||||
handleHtmlCloseElement(element, elementName, line, col);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlOpenElement(IHtmlElement element, String elementName, Map<String, String> attributes, int line, int col) {
|
||||
switch (elementName) {
|
||||
case "div": {
|
||||
if (attributes != null && "oauth_pin".equals(attributes.get("id"))) {
|
||||
isOAuthPinDivOpened = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleHtmlCloseElement(IHtmlElement element, String elementName, int line, int col) {
|
||||
if ("div".equals(elementName)) {
|
||||
isOAuthPinDivOpened = false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleText(char[] buffer, int offset, int len, int line, int col) {
|
||||
if (isOAuthPinDivOpened) {
|
||||
final String s = new String(buffer, offset, len);
|
||||
if (TextUtils.isDigitsOnly(s)) {
|
||||
data.oauthPin = s;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
PARSER.parse(reader, handler);
|
||||
}
|
||||
|
||||
public interface LoginVerificationCallback {
|
||||
String getLoginVerification(String challengeType);
|
||||
}
|
||||
|
||||
public static class AuthenticationException extends Exception {
|
||||
|
||||
public AuthenticationException() {
|
||||
}
|
||||
|
||||
public AuthenticationException(final Exception cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
public AuthenticationException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public AuthenticationException(final String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class AuthenticityTokenException extends AuthenticationException {
|
||||
|
||||
public AuthenticityTokenException(Exception e) {
|
||||
super(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class WrongUserPassException extends AuthenticationException {
|
||||
WrongUserPassException() {
|
||||
super();
|
||||
}
|
||||
|
||||
WrongUserPassException(Exception cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
WrongUserPassException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
WrongUserPassException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class LoginVerificationException extends AuthenticationException {
|
||||
LoginVerificationException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
LoginVerificationException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
LoginVerificationException(Exception cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
LoginVerificationException() {
|
||||
super();
|
||||
}
|
||||
}
|
||||
|
||||
static class AuthorizeResponseData {
|
||||
|
||||
String referer;
|
||||
|
||||
public String oauthPin;
|
||||
public Verification challenge;
|
||||
|
||||
static class Verification {
|
||||
|
||||
String authenticityToken;
|
||||
String challengeId;
|
||||
String challengeType;
|
||||
String platform;
|
||||
String userId;
|
||||
String redirectAfterLogin;
|
||||
}
|
||||
}
|
||||
|
||||
static class AuthorizeRequestData {
|
||||
public String authenticityToken;
|
||||
public String redirectAfterLogin;
|
||||
|
||||
public String referer;
|
||||
}
|
||||
|
||||
public static class OAuthPinData {
|
||||
|
||||
public String oauthPin;
|
||||
}
|
||||
|
||||
private static class EndpointInterceptor implements Interceptor {
|
||||
private final Endpoint endpoint;
|
||||
|
||||
public EndpointInterceptor(Endpoint endpoint) {
|
||||
this.endpoint = endpoint;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
final Response response = chain.proceed(chain.request());
|
||||
if (!response.isRedirect()) {
|
||||
return response;
|
||||
}
|
||||
final String location = response.header("Location");
|
||||
final Response.Builder builder = response.newBuilder();
|
||||
if (!TextUtils.isEmpty(location) && !endpoint.checkEndpoint(location)) {
|
||||
final HttpUrl originalLocation = HttpUrl.get(URI.create("https://api.twitter.com/").resolve(location));
|
||||
final HttpUrl.Builder locationBuilder = HttpUrl.parse(endpoint.getUrl()).newBuilder();
|
||||
for (String pathSegments : originalLocation.pathSegments()) {
|
||||
locationBuilder.addPathSegment(pathSegments);
|
||||
}
|
||||
for (int i = 0, j = originalLocation.querySize(); i < j; i++) {
|
||||
final String name = originalLocation.queryParameterName(i);
|
||||
final String value = originalLocation.queryParameterValue(i);
|
||||
locationBuilder.addQueryParameter(name, value);
|
||||
}
|
||||
final String encodedFragment = originalLocation.encodedFragment();
|
||||
if (encodedFragment != null) {
|
||||
locationBuilder.encodedFragment(encodedFragment);
|
||||
}
|
||||
final HttpUrl newLocation = locationBuilder.build();
|
||||
builder.header("Location", newLocation.toString());
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -542,7 +542,7 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
|
|||
class InputLoginVerificationDialogFragment : BaseDialogFragment(), DialogInterface.OnClickListener, DialogInterface.OnShowListener {
|
||||
|
||||
private var callback: SignInTask.InputLoginVerificationCallback? = null
|
||||
private var challengeType: String? = null
|
||||
var challengeType: String? = null
|
||||
|
||||
internal fun setCallback(callback: SignInTask.InputLoginVerificationCallback) {
|
||||
this.callback = callback
|
||||
|
@ -577,34 +577,36 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
|
|||
}
|
||||
}
|
||||
|
||||
fun setChallengeType(challengeType: String) {
|
||||
this.challengeType = challengeType
|
||||
}
|
||||
|
||||
override fun onShow(dialog: DialogInterface) {
|
||||
val alertDialog = dialog as AlertDialog
|
||||
val verificationHint = alertDialog.findViewById(R.id.verification_hint) as TextView?
|
||||
val editVerification = alertDialog.findViewById(R.id.edit_verification_code) as EditText?
|
||||
if (verificationHint == null || editVerification == null) return
|
||||
if ("Push".equals(challengeType!!, ignoreCase = true)) {
|
||||
verificationHint.setText(R.string.login_verification_push_hint)
|
||||
editVerification.visibility = View.GONE
|
||||
} else if ("RetypePhoneNumber".equals(challengeType!!, ignoreCase = true)) {
|
||||
verificationHint.setText(R.string.login_challenge_retype_phone_hint)
|
||||
editVerification.inputType = InputType.TYPE_CLASS_PHONE
|
||||
editVerification.visibility = View.VISIBLE
|
||||
} else if ("RetypeEmail".equals(challengeType!!, ignoreCase = true)) {
|
||||
verificationHint.setText(R.string.login_challenge_retype_email_hint)
|
||||
editVerification.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
editVerification.visibility = View.VISIBLE
|
||||
} else if ("Sms".equals(challengeType!!, ignoreCase = true)) {
|
||||
verificationHint.setText(R.string.login_verification_pin_hint)
|
||||
editVerification.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
editVerification.visibility = View.VISIBLE
|
||||
} else {
|
||||
verificationHint.text = getString(R.string.unsupported_login_verification_type_name,
|
||||
challengeType)
|
||||
editVerification.visibility = View.VISIBLE
|
||||
when {
|
||||
"Push".equals(challengeType, ignoreCase = true) -> {
|
||||
verificationHint.setText(R.string.login_verification_push_hint)
|
||||
editVerification.visibility = View.GONE
|
||||
}
|
||||
"RetypePhoneNumber".equals(challengeType, ignoreCase = true) -> {
|
||||
verificationHint.setText(R.string.login_challenge_retype_phone_hint)
|
||||
editVerification.inputType = InputType.TYPE_CLASS_PHONE
|
||||
editVerification.visibility = View.VISIBLE
|
||||
}
|
||||
"RetypeEmail".equals(challengeType, ignoreCase = true) -> {
|
||||
verificationHint.setText(R.string.login_challenge_retype_email_hint)
|
||||
editVerification.inputType = InputType.TYPE_CLASS_TEXT or InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
|
||||
editVerification.visibility = View.VISIBLE
|
||||
}
|
||||
"Sms".equals(challengeType, ignoreCase = true) -> {
|
||||
verificationHint.setText(R.string.login_verification_pin_hint)
|
||||
editVerification.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
|
||||
editVerification.visibility = View.VISIBLE
|
||||
}
|
||||
else -> {
|
||||
verificationHint.text = getString(R.string.unsupported_login_verification_type_name,
|
||||
challengeType)
|
||||
editVerification.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -933,7 +935,7 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
|
|||
field = value
|
||||
}
|
||||
|
||||
override fun getLoginVerification(challengeType: String): String? {
|
||||
override fun getLoginVerification(challengeType: String?): String? {
|
||||
// Dismiss current progress dialog
|
||||
publishProgress(Runnable {
|
||||
val activity = activityRef.get() ?: return@Runnable
|
||||
|
@ -947,7 +949,7 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher {
|
|||
val df = InputLoginVerificationDialogFragment()
|
||||
df.isCancelable = false
|
||||
df.setCallback(this@InputLoginVerificationCallback)
|
||||
df.setChallengeType(challengeType)
|
||||
df.challengeType = challengeType
|
||||
df.show(sia.supportFragmentManager, null)
|
||||
Unit
|
||||
}
|
||||
|
|
|
@ -0,0 +1,504 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2014 Mariotaku Lee <mariotaku.lee@gmail.com>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util
|
||||
|
||||
import android.text.TextUtils
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import org.attoparser.AttoParseException
|
||||
import org.attoparser.markup.MarkupAttoParser
|
||||
import org.attoparser.markup.html.AbstractStandardNonValidatingHtmlAttoHandler
|
||||
import org.attoparser.markup.html.HtmlParsingConfiguration
|
||||
import org.attoparser.markup.html.elements.IHtmlElement
|
||||
import org.mariotaku.microblog.library.MicroBlogException
|
||||
import org.mariotaku.microblog.library.twitter.TwitterOAuth
|
||||
import org.mariotaku.restfu.RestAPIFactory
|
||||
import org.mariotaku.restfu.annotation.method.GET
|
||||
import org.mariotaku.restfu.annotation.method.POST
|
||||
import org.mariotaku.restfu.http.*
|
||||
import org.mariotaku.restfu.http.mime.FormBody
|
||||
import org.mariotaku.restfu.http.mime.SimpleBody
|
||||
import org.mariotaku.restfu.oauth.OAuthToken
|
||||
import org.mariotaku.restfu.okhttp3.OkHttpRestClient
|
||||
import org.mariotaku.twidere.TwidereConstants.OAUTH_CALLBACK_OOB
|
||||
import org.mariotaku.twidere.util.net.SimpleCookieJar
|
||||
import java.io.IOException
|
||||
import java.io.Reader
|
||||
import java.net.URI
|
||||
|
||||
class OAuthPasswordAuthenticator(
|
||||
private val oauth: TwitterOAuth,
|
||||
private val loginVerificationCallback: OAuthPasswordAuthenticator.LoginVerificationCallback,
|
||||
private val userAgent: String
|
||||
) {
|
||||
private val client: RestHttpClient
|
||||
private val endpoint: Endpoint
|
||||
|
||||
init {
|
||||
val restClient = RestAPIFactory.getRestClient(oauth)
|
||||
this.endpoint = restClient.endpoint
|
||||
|
||||
val oldClient = (restClient.restClient as OkHttpRestClient).client
|
||||
val builder = oldClient.newBuilder()
|
||||
builder.cookieJar(SimpleCookieJar())
|
||||
builder.addNetworkInterceptor(EndpointInterceptor(endpoint))
|
||||
this.client = OkHttpRestClient(builder.build())
|
||||
}
|
||||
|
||||
@Throws(AuthenticationException::class)
|
||||
fun getOAuthAccessToken(username: String, password: String): OAuthToken {
|
||||
val requestToken: OAuthToken
|
||||
try {
|
||||
requestToken = oauth.getRequestToken(OAUTH_CALLBACK_OOB)
|
||||
} catch (e: MicroBlogException) {
|
||||
if (e.isCausedByNetworkIssue) throw AuthenticationException(e)
|
||||
throw AuthenticityTokenException(e)
|
||||
}
|
||||
|
||||
try {
|
||||
val authorizeRequestData = getAuthorizeRequestData(requestToken)
|
||||
var authorizeResponseData = getAuthorizeResponseData(requestToken,
|
||||
authorizeRequestData, username, password)
|
||||
if (!TextUtils.isEmpty(authorizeResponseData.oauthPin)) {
|
||||
// Here we got OAuth PIN, just get access token directly
|
||||
return oauth.getAccessToken(requestToken, authorizeResponseData.oauthPin)
|
||||
} else if (authorizeResponseData.challenge == null) {
|
||||
// No OAuth pin, or verification challenge, so treat as wrong password
|
||||
throw WrongUserPassException()
|
||||
}
|
||||
// Go to password verification flow
|
||||
val challengeType = authorizeResponseData.challenge!!.challengeType ?:
|
||||
throw LoginVerificationException()
|
||||
val loginVerification = loginVerificationCallback.getLoginVerification(challengeType)
|
||||
val verificationData = getVerificationData(authorizeResponseData,
|
||||
loginVerification)
|
||||
authorizeResponseData = getAuthorizeResponseData(requestToken,
|
||||
verificationData, username, password)
|
||||
if (TextUtils.isEmpty(authorizeResponseData.oauthPin)) {
|
||||
throw LoginVerificationException()
|
||||
}
|
||||
return oauth.getAccessToken(requestToken, authorizeResponseData.oauthPin)
|
||||
} catch (e: IOException) {
|
||||
throw AuthenticationException(e)
|
||||
} catch (e: NullPointerException) {
|
||||
throw AuthenticationException(e)
|
||||
} catch (e: MicroBlogException) {
|
||||
throw AuthenticationException(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Throws(IOException::class, LoginVerificationException::class)
|
||||
private fun getVerificationData(authorizeResponseData: AuthorizeResponseData,
|
||||
challengeResponse: String?): AuthorizeRequestData {
|
||||
var response: HttpResponse? = null
|
||||
try {
|
||||
val data = AuthorizeRequestData()
|
||||
val params = MultiValueMap<String>()
|
||||
val verification = authorizeResponseData.challenge!!
|
||||
params.add("authenticity_token", verification.authenticityToken)
|
||||
params.add("user_id", verification.userId)
|
||||
params.add("challenge_id", verification.challengeId)
|
||||
params.add("challenge_type", verification.challengeType)
|
||||
params.add("platform", verification.platform)
|
||||
params.add("redirect_after_login", verification.redirectAfterLogin)
|
||||
val requestHeaders = MultiValueMap<String>()
|
||||
requestHeaders.add("User-Agent", userAgent)
|
||||
|
||||
if (!TextUtils.isEmpty(challengeResponse)) {
|
||||
params.add("challenge_response", challengeResponse)
|
||||
}
|
||||
val authorizationResultBody = FormBody(params)
|
||||
|
||||
val authorizeResultBuilder = HttpRequest.Builder()
|
||||
authorizeResultBuilder.method(POST.METHOD)
|
||||
authorizeResultBuilder.url(endpoint.construct("/account/login_verification"))
|
||||
authorizeResultBuilder.headers(requestHeaders)
|
||||
authorizeResultBuilder.body(authorizationResultBody)
|
||||
response = client.newCall(authorizeResultBuilder.build()).execute()
|
||||
parseAuthorizeRequestData(response, data)
|
||||
if (TextUtils.isEmpty(data.authenticityToken)) {
|
||||
throw LoginVerificationException()
|
||||
}
|
||||
return data
|
||||
} catch (e: AttoParseException) {
|
||||
throw LoginVerificationException("Login verification challenge failed", e)
|
||||
} finally {
|
||||
Utils.closeSilently(response)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(AttoParseException::class, IOException::class)
|
||||
private fun parseAuthorizeRequestData(response: HttpResponse, data: AuthorizeRequestData) {
|
||||
val conf = HtmlParsingConfiguration()
|
||||
val handler = object : AbstractStandardNonValidatingHtmlAttoHandler(conf) {
|
||||
internal var isOAuthFormOpened: Boolean = false
|
||||
|
||||
override fun handleHtmlStandaloneElement(element: IHtmlElement?, minimized: Boolean,
|
||||
elementName: String?, attributes: Map<String, String>?,
|
||||
line: Int, col: Int) {
|
||||
handleHtmlOpenElement(element, elementName, attributes, line, col)
|
||||
handleHtmlCloseElement(element, elementName, line, col)
|
||||
}
|
||||
|
||||
override fun handleHtmlOpenElement(element: IHtmlElement?, elementName: String?,
|
||||
attributes: Map<String, String>?, line: Int, col: Int) {
|
||||
when (elementName) {
|
||||
"form" -> {
|
||||
if (attributes != null && "oauth_form" == attributes["id"]) {
|
||||
isOAuthFormOpened = true
|
||||
}
|
||||
}
|
||||
"input" -> {
|
||||
if (attributes != null && isOAuthFormOpened) {
|
||||
val name = attributes["name"]
|
||||
val value = attributes["value"]
|
||||
if (name == "authenticity_token") {
|
||||
data.authenticityToken = value
|
||||
} else if (name == "redirect_after_login") {
|
||||
data.redirectAfterLogin = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleHtmlCloseElement(element: IHtmlElement?, elementName: String?, line: Int, col: Int) {
|
||||
if ("form" == elementName) {
|
||||
isOAuthFormOpened = false
|
||||
}
|
||||
}
|
||||
}
|
||||
PARSER.parse(SimpleBody.reader(response.body), handler)
|
||||
}
|
||||
|
||||
@Throws(IOException::class, AuthenticationException::class)
|
||||
private fun getAuthorizeResponseData(requestToken: OAuthToken,
|
||||
authorizeRequestData: AuthorizeRequestData,
|
||||
username: String, password: String): AuthorizeResponseData {
|
||||
var response: HttpResponse? = null
|
||||
try {
|
||||
val data = AuthorizeResponseData()
|
||||
val params = MultiValueMap<String>()
|
||||
params.add("oauth_token", requestToken.oauthToken)
|
||||
params.add("authenticity_token", authorizeRequestData.authenticityToken)
|
||||
params.add("redirect_after_login", authorizeRequestData.redirectAfterLogin)
|
||||
if (!TextUtils.isEmpty(username) && !TextUtils.isEmpty(password)) {
|
||||
params.add("session[username_or_email]", username)
|
||||
params.add("session[password]", password)
|
||||
}
|
||||
val authorizationResultBody = FormBody(params)
|
||||
val requestHeaders = MultiValueMap<String>()
|
||||
requestHeaders.add("User-Agent", userAgent)
|
||||
data.referer = authorizeRequestData.referer
|
||||
|
||||
val authorizeResultBuilder = HttpRequest.Builder()
|
||||
authorizeResultBuilder.method(POST.METHOD)
|
||||
authorizeResultBuilder.url(endpoint.construct("/oauth/authorize"))
|
||||
authorizeResultBuilder.headers(requestHeaders)
|
||||
authorizeResultBuilder.body(authorizationResultBody)
|
||||
response = client.newCall(authorizeResultBuilder.build()).execute()
|
||||
val conf = HtmlParsingConfiguration()
|
||||
val handler = object : AbstractStandardNonValidatingHtmlAttoHandler(conf) {
|
||||
internal var isOAuthPinDivOpened: Boolean = false
|
||||
internal var isChallengeFormOpened: Boolean = false
|
||||
|
||||
override fun handleHtmlStandaloneElement(element: IHtmlElement?, minimized: Boolean,
|
||||
elementName: String?, attributes: Map<String, String>?,
|
||||
line: Int, col: Int) {
|
||||
handleHtmlOpenElement(element, elementName, attributes, line, col)
|
||||
handleHtmlCloseElement(element, elementName, line, col)
|
||||
}
|
||||
|
||||
override fun handleHtmlCloseElement(element: IHtmlElement?, elementName: String?, line: Int, col: Int) {
|
||||
when (elementName) {
|
||||
"div" -> {
|
||||
isOAuthPinDivOpened = false
|
||||
}
|
||||
"form" -> {
|
||||
isChallengeFormOpened = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleHtmlOpenElement(element: IHtmlElement?, elementName: String?,
|
||||
attributes: Map<String, String>?, line: Int, col: Int) {
|
||||
when (elementName) {
|
||||
"div" -> {
|
||||
if (attributes != null && "oauth_pin" == attributes["id"]) {
|
||||
isOAuthPinDivOpened = true
|
||||
}
|
||||
}
|
||||
"form" -> {
|
||||
if (attributes != null) when (attributes["id"]) {
|
||||
"login-verification-form", "login-challenge-form" -> {
|
||||
isChallengeFormOpened = true
|
||||
}
|
||||
}
|
||||
}
|
||||
"input" -> {
|
||||
if (attributes != null && isChallengeFormOpened) {
|
||||
val name = attributes["name"]
|
||||
val value = attributes["value"]
|
||||
when (name) {
|
||||
"authenticity_token" -> {
|
||||
ensureVerification()
|
||||
data.challenge!!.authenticityToken = value
|
||||
}
|
||||
"challenge_id" -> {
|
||||
ensureVerification()
|
||||
data.challenge!!.challengeId = value
|
||||
}
|
||||
"challenge_type" -> {
|
||||
ensureVerification()
|
||||
data.challenge!!.challengeType = value
|
||||
}
|
||||
"platform" -> {
|
||||
ensureVerification()
|
||||
data.challenge!!.platform = value
|
||||
}
|
||||
"user_id" -> {
|
||||
ensureVerification()
|
||||
data.challenge!!.userId = value
|
||||
}
|
||||
"redirect_after_login" -> {
|
||||
ensureVerification()
|
||||
data.challenge!!.redirectAfterLogin = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun ensureVerification() {
|
||||
if (data.challenge == null) {
|
||||
data.challenge = AuthorizeResponseData.Verification()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(AttoParseException::class)
|
||||
override fun handleText(buffer: CharArray?, offset: Int, len: Int, line: Int, col: Int) {
|
||||
if (isOAuthPinDivOpened) {
|
||||
val s = String(buffer!!, offset, len)
|
||||
if (TextUtils.isDigitsOnly(s)) {
|
||||
data.oauthPin = s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PARSER.parse(SimpleBody.reader(response!!.body), handler)
|
||||
return data
|
||||
} catch (e: AttoParseException) {
|
||||
throw AuthenticationException("Malformed HTML", e)
|
||||
} finally {
|
||||
Utils.closeSilently(response)
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(IOException::class, AuthenticationException::class)
|
||||
private fun getAuthorizeRequestData(requestToken: OAuthToken): AuthorizeRequestData {
|
||||
var response: HttpResponse? = null
|
||||
try {
|
||||
val data = AuthorizeRequestData()
|
||||
val authorizePageBuilder = HttpRequest.Builder()
|
||||
authorizePageBuilder.method(GET.METHOD)
|
||||
authorizePageBuilder.url(endpoint.construct("/oauth/authorize",
|
||||
arrayOf("oauth_token", requestToken.oauthToken)))
|
||||
data.referer = Endpoint.constructUrl("https://api.twitter.com/oauth/authorize",
|
||||
arrayOf("oauth_token", requestToken.oauthToken))
|
||||
val requestHeaders = MultiValueMap<String>()
|
||||
requestHeaders.add("User-Agent", userAgent)
|
||||
authorizePageBuilder.headers(requestHeaders)
|
||||
val authorizePageRequest = authorizePageBuilder.build()
|
||||
response = client.newCall(authorizePageRequest).execute()
|
||||
parseAuthorizeRequestData(response, data)
|
||||
if (TextUtils.isEmpty(data.authenticityToken)) {
|
||||
throw AuthenticationException()
|
||||
}
|
||||
return data
|
||||
} catch (e: AttoParseException) {
|
||||
throw AuthenticationException("Malformed HTML", e)
|
||||
} finally {
|
||||
Utils.closeSilently(response)
|
||||
}
|
||||
}
|
||||
|
||||
interface LoginVerificationCallback {
|
||||
fun getLoginVerification(challengeType: String): String?
|
||||
}
|
||||
|
||||
open class AuthenticationException : Exception {
|
||||
|
||||
constructor() {
|
||||
}
|
||||
|
||||
constructor(cause: Exception) : super(cause) {
|
||||
}
|
||||
|
||||
constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable) {
|
||||
}
|
||||
|
||||
constructor(message: String) : super(message) {
|
||||
}
|
||||
}
|
||||
|
||||
class AuthenticityTokenException(e: Exception) : AuthenticationException(e)
|
||||
|
||||
class WrongUserPassException : AuthenticationException {
|
||||
internal constructor() : super() {
|
||||
}
|
||||
|
||||
internal constructor(cause: Exception) : super(cause) {
|
||||
}
|
||||
|
||||
internal constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable) {
|
||||
}
|
||||
|
||||
internal constructor(message: String) : super(message) {
|
||||
}
|
||||
}
|
||||
|
||||
class LoginVerificationException : AuthenticationException {
|
||||
internal constructor(message: String) : super(message) {
|
||||
}
|
||||
|
||||
internal constructor(detailMessage: String, throwable: Throwable) : super(detailMessage, throwable) {
|
||||
}
|
||||
|
||||
internal constructor(cause: Exception) : super(cause) {
|
||||
}
|
||||
|
||||
internal constructor() : super() {
|
||||
}
|
||||
}
|
||||
|
||||
internal class AuthorizeResponseData {
|
||||
|
||||
var referer: String? = null
|
||||
|
||||
var oauthPin: String? = null
|
||||
var challenge: Verification? = null
|
||||
|
||||
internal class Verification {
|
||||
|
||||
var authenticityToken: String? = null
|
||||
var challengeId: String? = null
|
||||
var challengeType: String? = null
|
||||
var platform: String? = null
|
||||
var userId: String? = null
|
||||
var redirectAfterLogin: String? = null
|
||||
}
|
||||
}
|
||||
|
||||
internal class AuthorizeRequestData {
|
||||
var authenticityToken: String? = null
|
||||
var redirectAfterLogin: String? = null
|
||||
|
||||
var referer: String? = null
|
||||
}
|
||||
|
||||
class OAuthPinData {
|
||||
|
||||
var oauthPin: String? = null
|
||||
}
|
||||
|
||||
private class EndpointInterceptor(private val endpoint: Endpoint) : Interceptor {
|
||||
|
||||
@Throws(IOException::class)
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val response = chain.proceed(chain.request())
|
||||
if (!response.isRedirect) {
|
||||
return response
|
||||
}
|
||||
val location = response.header("Location")
|
||||
val builder = response.newBuilder()
|
||||
if (!TextUtils.isEmpty(location) && !endpoint.checkEndpoint(location)) {
|
||||
val originalLocation = HttpUrl.get(URI.create("https://api.twitter.com/").resolve(location))
|
||||
val locationBuilder = HttpUrl.parse(endpoint.url).newBuilder()
|
||||
for (pathSegments in originalLocation.pathSegments()) {
|
||||
locationBuilder.addPathSegment(pathSegments)
|
||||
}
|
||||
var i = 0
|
||||
val j = originalLocation.querySize()
|
||||
while (i < j) {
|
||||
val name = originalLocation.queryParameterName(i)
|
||||
val value = originalLocation.queryParameterValue(i)
|
||||
locationBuilder.addQueryParameter(name, value)
|
||||
i++
|
||||
}
|
||||
val encodedFragment = originalLocation.encodedFragment()
|
||||
if (encodedFragment != null) {
|
||||
locationBuilder.encodedFragment(encodedFragment)
|
||||
}
|
||||
val newLocation = locationBuilder.build()
|
||||
builder.header("Location", newLocation.toString())
|
||||
}
|
||||
return builder.build()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private val PARSER = MarkupAttoParser()
|
||||
|
||||
@Throws(AttoParseException::class, IOException::class)
|
||||
fun readOAuthPINFromHtml(reader: Reader, data: OAuthPinData) {
|
||||
val conf = HtmlParsingConfiguration()
|
||||
val handler = object : AbstractStandardNonValidatingHtmlAttoHandler(conf) {
|
||||
internal var isOAuthPinDivOpened: Boolean = false
|
||||
|
||||
override fun handleHtmlStandaloneElement(element: IHtmlElement?, minimized: Boolean,
|
||||
elementName: String?, attributes: Map<String, String>?,
|
||||
line: Int, col: Int) {
|
||||
handleHtmlOpenElement(element, elementName, attributes, line, col)
|
||||
handleHtmlCloseElement(element, elementName, line, col)
|
||||
}
|
||||
|
||||
override fun handleHtmlOpenElement(element: IHtmlElement?, elementName: String?, attributes: Map<String, String>?, line: Int, col: Int) {
|
||||
when (elementName) {
|
||||
"div" -> {
|
||||
if (attributes != null && "oauth_pin" == attributes["id"]) {
|
||||
isOAuthPinDivOpened = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleHtmlCloseElement(element: IHtmlElement?, elementName: String?, line: Int, col: Int) {
|
||||
if ("div" == elementName) {
|
||||
isOAuthPinDivOpened = false
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleText(buffer: CharArray?, offset: Int, len: Int, line: Int, col: Int) {
|
||||
if (isOAuthPinDivOpened) {
|
||||
val s = String(buffer!!, offset, len)
|
||||
if (TextUtils.isDigitsOnly(s)) {
|
||||
data.oauthPin = s
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
PARSER.parse(reader, handler)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue