commit for sync
|
@ -5,10 +5,12 @@ buildscript {
|
|||
repositories {
|
||||
jcenter()
|
||||
mavenCentral()
|
||||
maven { url 'https://raw.github.com/xujiaao/mvn-repository/master/releases' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.github.ben-manes:gradle-versions-plugin:0.7'
|
||||
classpath 'com.android.tools.build:gradle:1.0.1'
|
||||
classpath 'com.github.xujiaao:aarLinkSources:1.0.0'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
@ -40,6 +40,7 @@ android {
|
|||
dependencies {
|
||||
compile 'com.android.support:support-annotations:21.0.3'
|
||||
compile 'org.apache.commons:commons-lang3:3.3.2'
|
||||
compile 'com.squareup.retrofit:retrofit:1.9.0'
|
||||
compile project(':twidere.component.jsonserializer')
|
||||
compile project(':twidere.component.querybuilder')
|
||||
compile project(':twidere.component.twitter4j')
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 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.api;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
|
||||
import retrofit.RestAdapter;
|
||||
import retrofit.RestAdapter.Builder;
|
||||
import retrofit.converter.ConversionException;
|
||||
import retrofit.converter.Converter;
|
||||
import retrofit.mime.TypedInput;
|
||||
import retrofit.mime.TypedOutput;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/2/3.
|
||||
*/
|
||||
public class APIFactory {
|
||||
|
||||
public static TwitterAPI getTwitterAPI() {
|
||||
Builder builder = new RestAdapter.Builder();
|
||||
builder.setEndpoint("https://api.twitter.com");
|
||||
builder.setConverter(new ParcelableDataConverter());
|
||||
return builder.build().create(TwitterAPI.class);
|
||||
}
|
||||
|
||||
private static class ParcelableDataConverter implements Converter {
|
||||
@Override
|
||||
public Object fromBody(TypedInput typedInput, Type type) throws ConversionException {
|
||||
// Class<?> typeClass = (Class<?>) type;
|
||||
// typeClass.isAssignableFrom();
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedOutput toBody(Object o) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 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.api;
|
||||
|
||||
import org.mariotaku.twidere.model.ParcelableAccount;
|
||||
|
||||
import retrofit.http.GET;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/2/3.
|
||||
*/
|
||||
public interface TwitterAPI {
|
||||
|
||||
@GET("/account/verify_credentials.json")
|
||||
ParcelableAccount verifyCredentials();
|
||||
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 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.api;
|
||||
|
||||
import org.mariotaku.twidere.model.ParcelableAccount;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/2/3.
|
||||
*/
|
||||
public interface TwitterMediaAPI {
|
||||
|
||||
}
|
|
@ -26,6 +26,10 @@ import android.os.Parcel;
|
|||
import android.os.Parcelable;
|
||||
import android.support.annotation.NonNull;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import org.mariotaku.querybuilder.Columns.Column;
|
||||
import org.mariotaku.querybuilder.Expression;
|
||||
import org.mariotaku.querybuilder.RawItemArray;
|
||||
|
@ -33,6 +37,7 @@ import org.mariotaku.twidere.provider.TwidereDataStore.Accounts;
|
|||
import org.mariotaku.twidere.util.TwitterContentUtils;
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -362,4 +367,5 @@ public class ParcelableAccount implements Parcelable {
|
|||
+ ", same_oauth_signing_url=" + same_oauth_signing_url + "}";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -61,294 +61,293 @@ import static twitter4j.http.RequestMethod.POST;
|
|||
* @since Twitter4J 2.1.2
|
||||
*/
|
||||
public class HttpClientImpl extends HttpClientBase implements HttpClient, HttpResponseCode {
|
||||
private static final Logger logger = Logger.getLogger(HttpClientImpl.class);
|
||||
private static final Logger logger = Logger.getLogger(HttpClientImpl.class);
|
||||
|
||||
private static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[] { new TrustAllX509TrustManager() };
|
||||
private static final TrustManager[] TRUST_ALL_CERTS = new TrustManager[]{new TrustAllX509TrustManager()};
|
||||
|
||||
private static final SSLSocketFactory IGNORE_ERROR_SSL_FACTORY;
|
||||
private static final SSLSocketFactory IGNORE_ERROR_SSL_FACTORY;
|
||||
|
||||
static {
|
||||
System.setProperty("http.keepAlive", "false");
|
||||
SSLSocketFactory factory = null;
|
||||
try {
|
||||
final SSLContext sc = SSLContext.getInstance("TLS");
|
||||
sc.init(null, TRUST_ALL_CERTS, new SecureRandom());
|
||||
factory = sc.getSocketFactory();
|
||||
} catch (final KeyManagementException e) {
|
||||
} catch (final NoSuchAlgorithmException e) {
|
||||
}
|
||||
IGNORE_ERROR_SSL_FACTORY = factory;
|
||||
}
|
||||
static {
|
||||
System.setProperty("http.keepAlive", "false");
|
||||
SSLSocketFactory factory = null;
|
||||
try {
|
||||
final SSLContext sc = SSLContext.getInstance("TLS");
|
||||
sc.init(null, TRUST_ALL_CERTS, new SecureRandom());
|
||||
factory = sc.getSocketFactory();
|
||||
} catch (final KeyManagementException | NoSuchAlgorithmException e) {
|
||||
}
|
||||
IGNORE_ERROR_SSL_FACTORY = factory;
|
||||
}
|
||||
|
||||
private static final HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER = new AllowAllHostnameVerifier();
|
||||
private static final HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER = new AllowAllHostnameVerifier();
|
||||
|
||||
private static final Map<HttpClientConfiguration, HttpClient> instanceMap = new HashMap<HttpClientConfiguration, HttpClient>(
|
||||
1);
|
||||
private static final Map<HttpClientConfiguration, HttpClient> instanceMap = new HashMap<HttpClientConfiguration, HttpClient>(
|
||||
1);
|
||||
|
||||
public HttpClientImpl() {
|
||||
super(ConfigurationContext.getInstance());
|
||||
};
|
||||
public HttpClientImpl() {
|
||||
super(ConfigurationContext.getInstance());
|
||||
}
|
||||
|
||||
public HttpClientImpl(final HttpClientConfiguration conf) {
|
||||
super(conf);
|
||||
}
|
||||
public HttpClientImpl(final HttpClientConfiguration conf) {
|
||||
super(conf);
|
||||
}
|
||||
|
||||
public HttpResponse get(final String url, final String sign_url) throws TwitterException {
|
||||
return request(new HttpRequest(RequestMethod.GET, url, sign_url, null, null, null));
|
||||
}
|
||||
public HttpResponse get(final String url, final String sign_url) throws TwitterException {
|
||||
return request(new HttpRequest(RequestMethod.GET, url, sign_url, null, null, null));
|
||||
}
|
||||
|
||||
public HttpResponse post(final String url, final String sign_url, final HttpParameter[] params)
|
||||
throws TwitterException {
|
||||
return request(new HttpRequest(RequestMethod.POST, url, sign_url, params, null, null));
|
||||
}
|
||||
public HttpResponse post(final String url, final String sign_url, final HttpParameter[] params)
|
||||
throws TwitterException {
|
||||
return request(new HttpRequest(RequestMethod.POST, url, sign_url, params, null, null));
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse request(final HttpRequest req) throws TwitterException {
|
||||
int retriedCount;
|
||||
final int retry = CONF.getHttpRetryCount() + 1;
|
||||
HttpResponse res = null;
|
||||
for (retriedCount = 0; retriedCount < retry; retriedCount++) {
|
||||
int responseCode = -1;
|
||||
try {
|
||||
HttpURLConnection con;
|
||||
OutputStream os = null;
|
||||
try {
|
||||
con = getConnection(req.getURL());
|
||||
con.setDoInput(true);
|
||||
setHeaders(req, con);
|
||||
con.setRequestMethod(req.getMethod().name());
|
||||
final HttpParameter[] params = req.getParameters();
|
||||
if (req.getMethod() == POST) {
|
||||
if (HttpParameter.containsFile(params)) {
|
||||
String boundary = "----Twitter4J-upload" + System.currentTimeMillis();
|
||||
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
boundary = "--" + boundary;
|
||||
con.setDoOutput(true);
|
||||
os = con.getOutputStream();
|
||||
final DataOutputStream out = new DataOutputStream(os);
|
||||
for (final HttpParameter param : params) {
|
||||
if (param.isFile()) {
|
||||
write(out, boundary + "\r\n");
|
||||
write(out, "Content-Disposition: form-data; name=\"" + param.getName()
|
||||
+ "\"; filename=\"" + param.getFileName() + "\"\r\n");
|
||||
write(out, "Content-Type: " + param.getContentType() + "\r\n\r\n");
|
||||
final BufferedInputStream in = new BufferedInputStream(
|
||||
param.hasFileBody() ? param.getFileBody() : new FileInputStream(
|
||||
param.getFile()));
|
||||
int buff;
|
||||
while ((buff = in.read()) != -1) {
|
||||
out.write(buff);
|
||||
}
|
||||
write(out, "\r\n");
|
||||
in.close();
|
||||
} else {
|
||||
write(out, boundary + "\r\n");
|
||||
write(out, "Content-Disposition: form-data; name=\"" + param.getName() + "\"\r\n");
|
||||
write(out, "Content-Type: text/plain; charset=UTF-8\r\n\r\n");
|
||||
logger.debug(param.getValue());
|
||||
out.write(param.getValue().getBytes("UTF-8"));
|
||||
write(out, "\r\n");
|
||||
}
|
||||
}
|
||||
write(out, boundary + "--\r\n");
|
||||
write(out, "\r\n");
|
||||
@Override
|
||||
public HttpResponse request(final HttpRequest req) throws TwitterException {
|
||||
int retriedCount;
|
||||
final int retry = CONF.getHttpRetryCount() + 1;
|
||||
HttpResponse res = null;
|
||||
for (retriedCount = 0; retriedCount < retry; retriedCount++) {
|
||||
int responseCode = -1;
|
||||
try {
|
||||
HttpURLConnection con;
|
||||
OutputStream os = null;
|
||||
try {
|
||||
con = getConnection(req.getURL());
|
||||
con.setDoInput(true);
|
||||
setHeaders(req, con);
|
||||
con.setRequestMethod(req.getMethod().name());
|
||||
final HttpParameter[] params = req.getParameters();
|
||||
if (req.getMethod() == POST) {
|
||||
if (HttpParameter.containsFile(params)) {
|
||||
String boundary = "----Twitter4J-upload" + System.currentTimeMillis();
|
||||
con.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
|
||||
boundary = "--" + boundary;
|
||||
con.setDoOutput(true);
|
||||
os = con.getOutputStream();
|
||||
final DataOutputStream out = new DataOutputStream(os);
|
||||
for (final HttpParameter param : params) {
|
||||
if (param.isFile()) {
|
||||
write(out, boundary + "\r\n");
|
||||
write(out, "Content-Disposition: form-data; name=\"" + param.getName()
|
||||
+ "\"; filename=\"" + param.getFileName() + "\"\r\n");
|
||||
write(out, "Content-Type: " + param.getContentType() + "\r\n\r\n");
|
||||
final BufferedInputStream in = new BufferedInputStream(
|
||||
param.hasFileBody() ? param.getFileBody() : new FileInputStream(
|
||||
param.getFile()));
|
||||
int buff;
|
||||
while ((buff = in.read()) != -1) {
|
||||
out.write(buff);
|
||||
}
|
||||
write(out, "\r\n");
|
||||
in.close();
|
||||
} else {
|
||||
write(out, boundary + "\r\n");
|
||||
write(out, "Content-Disposition: form-data; name=\"" + param.getName() + "\"\r\n");
|
||||
write(out, "Content-Type: text/plain; charset=UTF-8\r\n\r\n");
|
||||
logger.debug(param.getValue());
|
||||
out.write(param.getValue().getBytes("UTF-8"));
|
||||
write(out, "\r\n");
|
||||
}
|
||||
}
|
||||
write(out, boundary + "--\r\n");
|
||||
write(out, "\r\n");
|
||||
|
||||
} else {
|
||||
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
final String postParam = HttpParameter.encodeParameters(req.getParameters());
|
||||
logger.debug("Post Params: ", postParam);
|
||||
final byte[] bytes = postParam.getBytes("UTF-8");
|
||||
con.setRequestProperty("Content-Length", Integer.toString(bytes.length));
|
||||
con.setDoOutput(true);
|
||||
os = con.getOutputStream();
|
||||
os.write(bytes);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
}
|
||||
res = new HttpResponseImpl(con, CONF);
|
||||
responseCode = con.getResponseCode();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Response: ");
|
||||
final Map<String, List<String>> responseHeaders = con.getHeaderFields();
|
||||
for (final String key : responseHeaders.keySet()) {
|
||||
final List<String> values = responseHeaders.get(key);
|
||||
for (final String value : values) {
|
||||
if (key != null) {
|
||||
logger.debug(key + ": " + value);
|
||||
} else {
|
||||
logger.debug(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (responseCode < OK || responseCode > ACCEPTED) {
|
||||
if (responseCode == ENHANCE_YOUR_CLAIM || responseCode == BAD_REQUEST
|
||||
|| responseCode < INTERNAL_SERVER_ERROR || retriedCount == CONF.getHttpRetryCount())
|
||||
throw new TwitterException(res.asString(), req, res);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (os != null) {
|
||||
os.close();
|
||||
}
|
||||
} catch (final IOException ignore) {
|
||||
}
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
// connection timeout or read timeout
|
||||
if (retriedCount == CONF.getHttpRetryCount())
|
||||
// throw new TwitterException(ioe.getMessage(), ioe,
|
||||
// responseCode);
|
||||
throw new TwitterException(ioe.getMessage(), req, res);
|
||||
} catch (final NullPointerException e) {
|
||||
// This exception will be thown when URL is invalid.
|
||||
e.printStackTrace();
|
||||
throw new TwitterException("The URL requested is invalid.", e);
|
||||
} catch (final OutOfMemoryError e) {
|
||||
throw new TwitterException(e.getMessage(), e);
|
||||
}
|
||||
try {
|
||||
if (logger.isDebugEnabled() && res != null) {
|
||||
res.asString();
|
||||
}
|
||||
logger.debug("Sleeping " + CONF.getHttpRetryIntervalSeconds() + " seconds until the next retry.");
|
||||
Thread.sleep(CONF.getHttpRetryIntervalSeconds() * 1000);
|
||||
} catch (final InterruptedException ignore) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
} else {
|
||||
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
|
||||
final String postParam = HttpParameter.encodeParameters(req.getParameters());
|
||||
logger.debug("Post Params: ", postParam);
|
||||
final byte[] bytes = postParam.getBytes("UTF-8");
|
||||
con.setRequestProperty("Content-Length", Integer.toString(bytes.length));
|
||||
con.setDoOutput(true);
|
||||
os = con.getOutputStream();
|
||||
os.write(bytes);
|
||||
}
|
||||
os.flush();
|
||||
os.close();
|
||||
}
|
||||
res = new HttpResponseImpl(con, CONF);
|
||||
responseCode = con.getResponseCode();
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Response: ");
|
||||
final Map<String, List<String>> responseHeaders = con.getHeaderFields();
|
||||
for (final String key : responseHeaders.keySet()) {
|
||||
final List<String> values = responseHeaders.get(key);
|
||||
for (final String value : values) {
|
||||
if (key != null) {
|
||||
logger.debug(key + ": " + value);
|
||||
} else {
|
||||
logger.debug(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (responseCode < OK || responseCode > ACCEPTED) {
|
||||
if (responseCode == ENHANCE_YOUR_CLAIM || responseCode == BAD_REQUEST
|
||||
|| responseCode < INTERNAL_SERVER_ERROR || retriedCount == CONF.getHttpRetryCount())
|
||||
throw new TwitterException(res.asString(), req, res);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
if (os != null) {
|
||||
os.close();
|
||||
}
|
||||
} catch (final IOException ignore) {
|
||||
}
|
||||
}
|
||||
} catch (final IOException ioe) {
|
||||
// connection timeout or read timeout
|
||||
if (retriedCount == CONF.getHttpRetryCount())
|
||||
// throw new TwitterException(ioe.getMessage(), ioe,
|
||||
// responseCode);
|
||||
throw new TwitterException(ioe.getMessage(), req, res);
|
||||
} catch (final NullPointerException e) {
|
||||
// This exception will be thown when URL is invalid.
|
||||
e.printStackTrace();
|
||||
throw new TwitterException("The URL requested is invalid.", e);
|
||||
} catch (final OutOfMemoryError e) {
|
||||
throw new TwitterException(e.getMessage(), e);
|
||||
}
|
||||
try {
|
||||
if (logger.isDebugEnabled() && res != null) {
|
||||
res.asString();
|
||||
}
|
||||
logger.debug("Sleeping " + CONF.getHttpRetryIntervalSeconds() + " seconds until the next retry.");
|
||||
Thread.sleep(CONF.getHttpRetryIntervalSeconds() * 1000);
|
||||
} catch (final InterruptedException ignore) {
|
||||
// nothing to do
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
private HttpURLConnection getConnection(final String url_string) throws IOException {
|
||||
private HttpURLConnection getConnection(final String url_string) throws IOException {
|
||||
|
||||
final HttpURLConnection con;
|
||||
final Proxy proxy;
|
||||
if (isProxyConfigured()) {
|
||||
if (CONF.getHttpProxyUser() != null && !CONF.getHttpProxyUser().equals("")) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Proxy AuthUser: " + CONF.getHttpProxyUser());
|
||||
logger.debug("Proxy AuthPassword: " + InternalStringUtil.maskString(CONF.getHttpProxyPassword()));
|
||||
}
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
// respond only to proxy auth requests
|
||||
if (getRequestorType().equals(RequestorType.PROXY))
|
||||
return new PasswordAuthentication(CONF.getHttpProxyUser(), CONF.getHttpProxyPassword()
|
||||
.toCharArray());
|
||||
else
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
proxy = new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(CONF.getHttpProxyHost(),
|
||||
CONF.getHttpProxyPort()));
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Opening proxied connection(" + CONF.getHttpProxyHost() + ":" + CONF.getHttpProxyPort()
|
||||
+ ")");
|
||||
}
|
||||
} else {
|
||||
proxy = Proxy.NO_PROXY;
|
||||
}
|
||||
final HostAddressResolver resolver = FactoryUtils.getHostAddressResolver(CONF);
|
||||
final URI url_orig;
|
||||
try {
|
||||
url_orig = new URI(url_string);
|
||||
} catch (final URISyntaxException e) {
|
||||
throw new IOException("Invalid URI " + url_string);
|
||||
}
|
||||
final String host = url_orig.getHost(), authority = url_orig.getAuthority();
|
||||
final String resolved_host = resolver != null ? resolver.resolve(host) : null;
|
||||
con = (HttpURLConnection) new URL(resolved_host != null ? url_string.replace("://" + host, "://"
|
||||
+ resolved_host) : url_string).openConnection(proxy);
|
||||
if (resolved_host != null && !host.equals(resolved_host)) {
|
||||
con.setRequestProperty("Host", authority);
|
||||
}
|
||||
if (CONF.getHttpConnectionTimeout() > 0) {
|
||||
con.setConnectTimeout(CONF.getHttpConnectionTimeout());
|
||||
}
|
||||
if (CONF.getHttpReadTimeout() > 0) {
|
||||
con.setReadTimeout(CONF.getHttpReadTimeout());
|
||||
}
|
||||
con.setInstanceFollowRedirects(false);
|
||||
if (con instanceof HttpsURLConnection && CONF.isSSLErrorIgnored()) {
|
||||
((HttpsURLConnection) con).setHostnameVerifier(ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||
if (IGNORE_ERROR_SSL_FACTORY != null) {
|
||||
((HttpsURLConnection) con).setSSLSocketFactory(IGNORE_ERROR_SSL_FACTORY);
|
||||
}
|
||||
}
|
||||
return con;
|
||||
}
|
||||
final HttpURLConnection con;
|
||||
final Proxy proxy;
|
||||
if (isProxyConfigured()) {
|
||||
if (CONF.getHttpProxyUser() != null && !CONF.getHttpProxyUser().equals("")) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Proxy AuthUser: " + CONF.getHttpProxyUser());
|
||||
logger.debug("Proxy AuthPassword: " + InternalStringUtil.maskString(CONF.getHttpProxyPassword()));
|
||||
}
|
||||
Authenticator.setDefault(new Authenticator() {
|
||||
@Override
|
||||
protected PasswordAuthentication getPasswordAuthentication() {
|
||||
// respond only to proxy auth requests
|
||||
if (getRequestorType().equals(RequestorType.PROXY))
|
||||
return new PasswordAuthentication(CONF.getHttpProxyUser(), CONF.getHttpProxyPassword()
|
||||
.toCharArray());
|
||||
else
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
proxy = new Proxy(Proxy.Type.HTTP, InetSocketAddress.createUnresolved(CONF.getHttpProxyHost(),
|
||||
CONF.getHttpProxyPort()));
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Opening proxied connection(" + CONF.getHttpProxyHost() + ":" + CONF.getHttpProxyPort()
|
||||
+ ")");
|
||||
}
|
||||
} else {
|
||||
proxy = Proxy.NO_PROXY;
|
||||
}
|
||||
final HostAddressResolver resolver = FactoryUtils.getHostAddressResolver(CONF);
|
||||
final URI url_orig;
|
||||
try {
|
||||
url_orig = new URI(url_string);
|
||||
} catch (final URISyntaxException e) {
|
||||
throw new IOException("Invalid URI " + url_string);
|
||||
}
|
||||
final String host = url_orig.getHost(), authority = url_orig.getAuthority();
|
||||
final String resolved_host = resolver != null ? resolver.resolve(host) : null;
|
||||
con = (HttpURLConnection) new URL(resolved_host != null ? url_string.replace("://" + host, "://"
|
||||
+ resolved_host) : url_string).openConnection(proxy);
|
||||
if (resolved_host != null && !host.equals(resolved_host)) {
|
||||
con.setRequestProperty("Host", authority);
|
||||
}
|
||||
if (CONF.getHttpConnectionTimeout() > 0) {
|
||||
con.setConnectTimeout(CONF.getHttpConnectionTimeout());
|
||||
}
|
||||
if (CONF.getHttpReadTimeout() > 0) {
|
||||
con.setReadTimeout(CONF.getHttpReadTimeout());
|
||||
}
|
||||
con.setInstanceFollowRedirects(false);
|
||||
if (con instanceof HttpsURLConnection && CONF.isSSLErrorIgnored()) {
|
||||
((HttpsURLConnection) con).setHostnameVerifier(ALLOW_ALL_HOSTNAME_VERIFIER);
|
||||
if (IGNORE_ERROR_SSL_FACTORY != null) {
|
||||
((HttpsURLConnection) con).setSSLSocketFactory(IGNORE_ERROR_SSL_FACTORY);
|
||||
}
|
||||
}
|
||||
return con;
|
||||
}
|
||||
|
||||
/**
|
||||
* sets HTTP headers
|
||||
*
|
||||
* @param req The request
|
||||
* @param connection HttpURLConnection
|
||||
*/
|
||||
private void setHeaders(final HttpRequest req, final HttpURLConnection connection) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Request: ");
|
||||
logger.debug(req.getMethod().name() + " ", req.getURL());
|
||||
}
|
||||
/**
|
||||
* sets HTTP headers
|
||||
*
|
||||
* @param req The request
|
||||
* @param connection HttpURLConnection
|
||||
*/
|
||||
private void setHeaders(final HttpRequest req, final HttpURLConnection connection) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Request: ");
|
||||
logger.debug(req.getMethod().name() + " ", req.getURL());
|
||||
}
|
||||
|
||||
String authorizationHeader;
|
||||
if (req.getAuthorization() != null
|
||||
&& (authorizationHeader = req.getAuthorization().getAuthorizationHeader(req)) != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Authorization: ", InternalStringUtil.maskString(authorizationHeader));
|
||||
}
|
||||
connection.addRequestProperty("Authorization", authorizationHeader);
|
||||
}
|
||||
final Map<String, String> req_headers = req.getRequestHeaders();
|
||||
if (req_headers != null) {
|
||||
for (final String key : req_headers.keySet()) {
|
||||
connection.addRequestProperty(key, req.getRequestHeaders().get(key));
|
||||
logger.debug(key + ": " + req.getRequestHeaders().get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
String authorizationHeader;
|
||||
if (req.getAuthorization() != null
|
||||
&& (authorizationHeader = req.getAuthorization().getAuthorizationHeader(req)) != null) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Authorization: ", InternalStringUtil.maskString(authorizationHeader));
|
||||
}
|
||||
connection.addRequestProperty("Authorization", authorizationHeader);
|
||||
}
|
||||
final Map<String, String> req_headers = req.getRequestHeaders();
|
||||
if (req_headers != null) {
|
||||
for (final String key : req_headers.keySet()) {
|
||||
connection.addRequestProperty(key, req.getRequestHeaders().get(key));
|
||||
logger.debug(key + ": " + req.getRequestHeaders().get(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static String encode(final String str) {
|
||||
try {
|
||||
return URLEncoder.encode(str, "UTF-8");
|
||||
} catch (final java.io.UnsupportedEncodingException neverHappen) {
|
||||
throw new AssertionError("will never happen");
|
||||
}
|
||||
}
|
||||
public static String encode(final String str) {
|
||||
try {
|
||||
return URLEncoder.encode(str, "UTF-8");
|
||||
} catch (final java.io.UnsupportedEncodingException neverHappen) {
|
||||
throw new AssertionError("will never happen");
|
||||
}
|
||||
}
|
||||
|
||||
public static HttpClient getInstance(final HttpClientConfiguration conf) {
|
||||
HttpClient client = instanceMap.get(conf);
|
||||
if (null == client) {
|
||||
client = new HttpClientImpl(conf);
|
||||
instanceMap.put(conf, client);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
public static HttpClient getInstance(final HttpClientConfiguration conf) {
|
||||
HttpClient client = instanceMap.get(conf);
|
||||
if (null == client) {
|
||||
client = new HttpClientImpl(conf);
|
||||
instanceMap.put(conf, client);
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
static class AllowAllHostnameVerifier implements HostnameVerifier {
|
||||
@Override
|
||||
public boolean verify(final String hostname, final SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
static class AllowAllHostnameVerifier implements HostnameVerifier {
|
||||
@Override
|
||||
public boolean verify(final String hostname, final SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
final static class TrustAllX509TrustManager implements X509TrustManager {
|
||||
@Override
|
||||
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
|
||||
}
|
||||
final static class TrustAllX509TrustManager implements X509TrustManager {
|
||||
@Override
|
||||
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(final X509Certificate[] chain, final String authType) {
|
||||
}
|
||||
@Override
|
||||
public void checkServerTrusted(final X509Certificate[] chain, final String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[] {};
|
||||
}
|
||||
}
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[]{};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import java.text.SimpleDateFormat
|
||||
|
||||
apply plugin: 'com.android.application'
|
||||
apply plugin: 'aar-link-sources'
|
||||
apply from: rootProject.file('signing.gradle')
|
||||
|
||||
android {
|
||||
|
@ -62,18 +63,18 @@ dependencies {
|
|||
compile 'com.android.support:palette-v7:21.0.3'
|
||||
compile 'com.sothree.slidinguppanel:library:2.0.4'
|
||||
compile 'com.twitter:twitter-text:1.9.9'
|
||||
aarLinkSources 'com.twitter:twitter-text:1.9.9:sources@jar'
|
||||
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.3'
|
||||
compile 'org.apache.httpcomponents:httpclient-android:4.3.5'
|
||||
compile 'org.apache.httpcomponents:httpmime:4.3.5'
|
||||
compile 'org.apache.commons:commons-csv:1.1'
|
||||
compile 'com.google.android.apps.dashclock:dashclock-api:2.0.0'
|
||||
compile 'com.squareup:otto:1.3.5'
|
||||
compile 'com.squareup:otto:1.3.6'
|
||||
compile 'dnsjava:dnsjava:2.1.6'
|
||||
compile 'com.commonsware.cwac:merge:1.1.1'
|
||||
compile 'com.diegocarloslima:byakugallery:0.1.0'
|
||||
compile 'com.rengwuxian.materialedittext:library:1.8.2'
|
||||
compile 'com.pnikosis:materialish-progress:1.4'
|
||||
compile 'com.squareup.okhttp:okhttp:2.2.0'
|
||||
aarLinkSources 'com.squareup.okhttp:okhttp:2.2.0:sources@jar'
|
||||
googleCompile 'com.google.android.gms:play-services:6.5.87'
|
||||
googleCompile 'com.google.maps.android:android-maps-utils:0.3.4'
|
||||
fdroidCompile 'org.osmdroid:osmdroid-android:4.3'
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest package="org.mariotaku.twidere"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:installLocation="auto">
|
||||
<manifest
|
||||
package="org.mariotaku.twidere"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:installLocation="auto">
|
||||
|
||||
<uses-sdk/>
|
||||
|
||||
|
@ -569,9 +570,8 @@
|
|||
</activity>
|
||||
<activity
|
||||
android:name=".activity.TestActivity"
|
||||
android:enabled="true"
|
||||
android:enabled="false"
|
||||
android:launchMode="singleTop"
|
||||
android:theme="@android:style/Theme.DeviceDefault.Dialog"
|
||||
android:label="Twidere test"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
|
|
|
@ -27,6 +27,7 @@ import android.graphics.Bitmap;
|
|||
import android.net.Uri;
|
||||
import android.net.http.SslError;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
|
@ -45,8 +46,8 @@ import org.mariotaku.twidere.util.OAuthPasswordAuthenticator;
|
|||
import org.mariotaku.twidere.util.ParseUtils;
|
||||
import org.mariotaku.twidere.util.TwitterContentUtils;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
import org.mariotaku.twidere.util.net.ApacheHttpClientFactory;
|
||||
import org.mariotaku.twidere.util.net.TwidereHostResolverFactory;
|
||||
import org.mariotaku.twidere.util.net.OkHttpClientFactory;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -134,9 +135,7 @@ public class BrowserSignInActivity extends BaseSupportDialogActivity implements
|
|||
private String readOAuthPin(final String html) {
|
||||
try {
|
||||
return OAuthPasswordAuthenticator.readOAuthPINFromHtml(new StringReader(html));
|
||||
} catch (final XmlPullParserException e) {
|
||||
e.printStackTrace();
|
||||
} catch (final IOException e) {
|
||||
} catch (final XmlPullParserException | IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
|
@ -179,7 +178,7 @@ public class BrowserSignInActivity extends BaseSupportDialogActivity implements
|
|||
}
|
||||
|
||||
@Override
|
||||
public void onReceivedSslError(final WebView view, final SslErrorHandler handler, final SslError error) {
|
||||
public void onReceivedSslError(final WebView view, @NonNull final SslErrorHandler handler, final SslError error) {
|
||||
if (mActivity.mPreferences.getBoolean(KEY_IGNORE_SSL_ERROR, false)) {
|
||||
handler.proceed();
|
||||
} else {
|
||||
|
@ -234,7 +233,7 @@ public class BrowserSignInActivity extends BaseSupportDialogActivity implements
|
|||
final String consumerSecret = getNonEmptyString(mPreferences, KEY_CONSUMER_SECRET,
|
||||
TWITTER_CONSUMER_SECRET_3);
|
||||
cb.setHostAddressResolverFactory(new TwidereHostResolverFactory(mApplication));
|
||||
cb.setHttpClientFactory(new ApacheHttpClientFactory(mApplication));
|
||||
cb.setHttpClientFactory(new OkHttpClientFactory(mApplication));
|
||||
if (TwitterContentUtils.isOfficialKey(mActivity, consumerKey, consumerSecret)) {
|
||||
Utils.setMockOfficialUserAgent(mActivity, cb);
|
||||
} else {
|
||||
|
@ -301,13 +300,13 @@ public class BrowserSignInActivity extends BaseSupportDialogActivity implements
|
|||
|
||||
@JavascriptInterface
|
||||
public void processHTML(final String html) {
|
||||
final String oauth_verifier = mActivity.readOAuthPin(html);
|
||||
final RequestToken request_token = mActivity.mRequestToken;
|
||||
if (oauth_verifier != null && request_token != null) {
|
||||
final String oauthVerifier = mActivity.readOAuthPin(html);
|
||||
final RequestToken requestToken = mActivity.mRequestToken;
|
||||
if (oauthVerifier != null && requestToken != null) {
|
||||
final Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_OAUTH_VERIFIER, oauth_verifier);
|
||||
intent.putExtra(EXTRA_REQUEST_TOKEN, request_token.getToken());
|
||||
intent.putExtra(EXTRA_REQUEST_TOKEN_SECRET, request_token.getTokenSecret());
|
||||
intent.putExtra(EXTRA_OAUTH_VERIFIER, oauthVerifier);
|
||||
intent.putExtra(EXTRA_REQUEST_TOKEN, requestToken.getToken());
|
||||
intent.putExtra(EXTRA_REQUEST_TOKEN_SECRET, requestToken.getTokenSecret());
|
||||
mActivity.setResult(RESULT_OK, intent);
|
||||
mActivity.finish();
|
||||
}
|
||||
|
|
|
@ -62,7 +62,6 @@ import android.support.v7.widget.RecyclerView.Adapter;
|
|||
import android.support.v7.widget.RecyclerView.ItemDecoration;
|
||||
import android.support.v7.widget.RecyclerView.State;
|
||||
import android.support.v7.widget.RecyclerView.ViewHolder;
|
||||
import android.support.v7.widget.Toolbar;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
|
@ -195,7 +194,6 @@ public class ComposeActivity extends ThemedActionBarActivity implements TextWatc
|
|||
private View mLocationContainer;
|
||||
private ActionIconView mLocationIcon;
|
||||
private SupportMenuInflater mMenuInflater;
|
||||
private Toolbar mToolbar;
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
|
||||
|
@ -536,7 +534,6 @@ public class ComposeActivity extends ThemedActionBarActivity implements TextWatc
|
|||
@Override
|
||||
public void onSupportContentChanged() {
|
||||
super.onSupportContentChanged();
|
||||
mToolbar = (Toolbar) findViewById(R.id.compose_actionbar);
|
||||
mEditText = (EditText) findViewById(R.id.edit_text);
|
||||
mMediaPreviewGrid = (GridView) findViewById(R.id.media_thumbnail_preview);
|
||||
mMenuBar = (ActionMenuView) findViewById(R.id.menu_bar);
|
||||
|
@ -587,7 +584,6 @@ public class ComposeActivity extends ThemedActionBarActivity implements TextWatc
|
|||
mValidator = new TwidereValidator(this);
|
||||
mImageLoader = app.getImageLoaderWrapper();
|
||||
setContentView(R.layout.activity_compose);
|
||||
setSupportActionBar(mToolbar);
|
||||
setSupportProgressBarIndeterminateVisibility(false);
|
||||
setFinishOnTouchOutside(false);
|
||||
final long[] defaultAccountIds = getAccountIds(this);
|
||||
|
|
|
@ -61,8 +61,8 @@ import org.mariotaku.twidere.util.ThemeUtils;
|
|||
import org.mariotaku.twidere.util.TwitterContentUtils;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
import org.mariotaku.twidere.util.accessor.ViewAccessor;
|
||||
import org.mariotaku.twidere.util.net.ApacheHttpClientFactory;
|
||||
import org.mariotaku.twidere.util.net.TwidereHostResolverFactory;
|
||||
import org.mariotaku.twidere.util.net.OkHttpClientFactory;
|
||||
|
||||
import twitter4j.Twitter;
|
||||
import twitter4j.TwitterConstants;
|
||||
|
@ -355,7 +355,7 @@ public class SignInActivity extends BaseSupportActivity implements TwitterConsta
|
|||
final boolean ignore_ssl_error = mPreferences.getBoolean(KEY_IGNORE_SSL_ERROR, false);
|
||||
final boolean enable_proxy = mPreferences.getBoolean(KEY_ENABLE_PROXY, false);
|
||||
cb.setHostAddressResolverFactory(new TwidereHostResolverFactory(mApplication));
|
||||
cb.setHttpClientFactory(new ApacheHttpClientFactory(mApplication));
|
||||
cb.setHttpClientFactory(new OkHttpClientFactory(mApplication));
|
||||
if (TwitterContentUtils.isOfficialKey(this, mConsumerKey, mConsumerSecret)) {
|
||||
Utils.setMockOfficialUserAgent(this, cb);
|
||||
} else {
|
||||
|
|
|
@ -202,9 +202,8 @@ import org.mariotaku.twidere.provider.TwidereDataStore.UnreadCounts;
|
|||
import org.mariotaku.twidere.service.RefreshService;
|
||||
import org.mariotaku.twidere.util.content.ContentResolverUtils;
|
||||
import org.mariotaku.twidere.util.menu.TwidereMenuInfo;
|
||||
import org.mariotaku.twidere.util.net.ApacheHttpClientFactory;
|
||||
import org.mariotaku.twidere.util.net.TwidereHostResolverFactory;
|
||||
import org.mariotaku.twidere.util.net.ssl.OkHttpClientFactory;
|
||||
import org.mariotaku.twidere.util.net.OkHttpClientFactory;
|
||||
import org.mariotaku.twidere.view.ShapedImageView;
|
||||
import org.mariotaku.twidere.view.ShapedImageView.ShapeStyle;
|
||||
|
||||
|
@ -1801,8 +1800,7 @@ public final class Utils implements Constants, TwitterConstants {
|
|||
if (userAgent != null) {
|
||||
cb.setHttpUserAgent(userAgent);
|
||||
}
|
||||
cb.setHttpClientFactory(new ApacheHttpClientFactory(context));
|
||||
// cb.setHttpClientFactory(new OkHttpClientFactory());
|
||||
cb.setHttpClientFactory(new OkHttpClientFactory(context));
|
||||
return new HttpClientWrapper(cb.build());
|
||||
}
|
||||
|
||||
|
@ -2527,8 +2525,7 @@ public final class Utils implements Constants, TwitterConstants {
|
|||
final ConfigurationBuilder cb = new ConfigurationBuilder();
|
||||
cb.setHostAddressResolverFactory(new TwidereHostResolverFactory(app));
|
||||
if (apacheHttp) {
|
||||
cb.setHttpClientFactory(new ApacheHttpClientFactory(app));
|
||||
// cb.setHttpClientFactory(new OkHttpClientFactory());
|
||||
cb.setHttpClientFactory(new OkHttpClientFactory(context));
|
||||
}
|
||||
cb.setHttpConnectionTimeout(connection_timeout);
|
||||
cb.setGZIPEnabled(enableGzip);
|
||||
|
|
|
@ -1,22 +0,0 @@
|
|||
package org.mariotaku.twidere.util.net;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import twitter4j.http.HttpClient;
|
||||
import twitter4j.http.HttpClientConfiguration;
|
||||
import twitter4j.http.HttpClientFactory;
|
||||
|
||||
public class ApacheHttpClientFactory implements HttpClientFactory {
|
||||
|
||||
private final Context context;
|
||||
|
||||
public ApacheHttpClientFactory(final Context context) {
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getInstance(final HttpClientConfiguration conf) {
|
||||
return new ApacheHttpClientImpl(context, conf);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
/*
|
||||
* Copyright 2007 Yusuke Yamamoto
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util.net;
|
||||
|
||||
import org.apache.http.Header;
|
||||
import org.apache.http.HeaderElement;
|
||||
import org.apache.http.HttpResponse;
|
||||
|
||||
import twitter4j.http.HttpClientConfiguration;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
/**
|
||||
* @author Yusuke Yamamoto - yusuke at mac.com
|
||||
* @since Twitter4J 2.1.2
|
||||
*/
|
||||
final class ApacheHttpClientHttpResponseImpl extends twitter4j.http.HttpResponse {
|
||||
private final HttpResponse res;
|
||||
|
||||
ApacheHttpClientHttpResponseImpl(final HttpResponse res, final HttpClientConfiguration conf) throws IOException {
|
||||
super(conf);
|
||||
this.res = res;
|
||||
is = res.getEntity().getContent();
|
||||
statusCode = res.getStatusLine().getStatusCode();
|
||||
if (is != null && "gzip".equals(getResponseHeader("Content-Encoding"))) {
|
||||
// the response is gzipped
|
||||
is = new GZIPInputStream(is);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public void disconnect() throws IOException {
|
||||
if (res != null) {
|
||||
res.getEntity().consumeContent();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public final String getResponseHeader(final String name) {
|
||||
final Header[] headers = res.getHeaders(name);
|
||||
if (headers != null && headers.length > 0)
|
||||
return headers[0].getValue();
|
||||
else
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getResponseHeaderFields() {
|
||||
final Header[] headers = res.getAllHeaders();
|
||||
final Map<String, List<String>> maps = new HashMap<String, List<String>>();
|
||||
for (final Header header : headers) {
|
||||
final HeaderElement[] elements = header.getElements();
|
||||
final List<String> values = new ArrayList<String>(1);
|
||||
for (final HeaderElement element : elements) {
|
||||
values.add(element.getValue());
|
||||
}
|
||||
maps.put(header.getName(), values);
|
||||
}
|
||||
return maps;
|
||||
}
|
||||
}
|
|
@ -1,222 +0,0 @@
|
|||
/*
|
||||
* Copyright 2007 Yusuke Yamamoto
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util.net;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.apache.http.Consts;
|
||||
import org.apache.http.HttpEntity;
|
||||
import org.apache.http.HttpHeaders;
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.auth.AuthScope;
|
||||
import org.apache.http.auth.UsernamePasswordCredentials;
|
||||
import org.apache.http.client.CredentialsProvider;
|
||||
import org.apache.http.client.config.RequestConfig;
|
||||
import org.apache.http.client.methods.HttpDeleteHC4;
|
||||
import org.apache.http.client.methods.HttpGetHC4;
|
||||
import org.apache.http.client.methods.HttpHeadHC4;
|
||||
import org.apache.http.client.methods.HttpPostHC4;
|
||||
import org.apache.http.client.methods.HttpPutHC4;
|
||||
import org.apache.http.client.methods.HttpRequestBaseHC4;
|
||||
import org.apache.http.client.params.HttpClientParams;
|
||||
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.mime.MultipartEntityBuilder;
|
||||
import org.apache.http.entity.mime.content.ContentBody;
|
||||
import org.apache.http.entity.mime.content.FileBody;
|
||||
import org.apache.http.entity.mime.content.InputStreamBody;
|
||||
import org.apache.http.entity.mime.content.StringBody;
|
||||
import org.apache.http.impl.client.BasicCredentialsProvider;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClientBuilder;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.params.CoreProtocolPNames;
|
||||
import org.apache.http.params.HttpParams;
|
||||
import org.apache.http.protocol.BasicHttpContextHC4;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.TextUtils;
|
||||
import org.mariotaku.twidere.util.ParseUtils;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
import org.mariotaku.twidere.util.net.ssl.HostResolvedSSLConnectionSocketFactory;
|
||||
import org.mariotaku.twidere.util.net.ssl.TwidereSSLSocketFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
import twitter4j.TwitterException;
|
||||
import twitter4j.auth.Authorization;
|
||||
import twitter4j.http.FactoryUtils;
|
||||
import twitter4j.http.HostAddressResolver;
|
||||
import twitter4j.http.HttpClientConfiguration;
|
||||
import twitter4j.http.HttpParameter;
|
||||
import twitter4j.http.HttpResponseCode;
|
||||
import twitter4j.http.RequestMethod;
|
||||
import twitter4j.internal.logging.Logger;
|
||||
import twitter4j.internal.util.InternalStringUtil;
|
||||
|
||||
import static android.text.TextUtils.isEmpty;
|
||||
|
||||
/**
|
||||
* HttpClient implementation for Apache HttpClient 4.0.x
|
||||
*
|
||||
* @author Yusuke Yamamoto - yusuke at mac.com
|
||||
* @since Twitter4J 2.1.2
|
||||
*/
|
||||
public class ApacheHttpClientImpl implements twitter4j.http.HttpClient, HttpResponseCode {
|
||||
private static final Logger logger = Logger.getLogger(ApacheHttpClientImpl.class);
|
||||
private final HttpClientConfiguration conf;
|
||||
private final CloseableHttpClient client;
|
||||
|
||||
public ApacheHttpClientImpl(final Context context, final HttpClientConfiguration conf) {
|
||||
this.conf = conf;
|
||||
final HttpClientBuilder clientBuilder = HttpClients.custom();
|
||||
final LayeredConnectionSocketFactory factory = TwidereSSLSocketFactory.getSocketFactory(context,
|
||||
conf.isSSLErrorIgnored());
|
||||
clientBuilder.setSSLSocketFactory(factory);
|
||||
final RequestConfig.Builder requestConfigBuilder = RequestConfig.custom();
|
||||
requestConfigBuilder.setConnectionRequestTimeout(conf.getHttpConnectionTimeout());
|
||||
requestConfigBuilder.setConnectTimeout(conf.getHttpConnectionTimeout());
|
||||
requestConfigBuilder.setSocketTimeout(conf.getHttpReadTimeout());
|
||||
requestConfigBuilder.setRedirectsEnabled(false);
|
||||
clientBuilder.setDefaultRequestConfig(requestConfigBuilder.build());
|
||||
if (conf.isProxyConfigured()) {
|
||||
final HttpHost proxy = new HttpHost(conf.getHttpProxyHost(), conf.getHttpProxyPort());
|
||||
clientBuilder.setProxy(proxy);
|
||||
if (!TextUtils.isEmpty(conf.getHttpProxyUser())) {
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug("Proxy AuthUser: " + conf.getHttpProxyUser());
|
||||
logger.debug("Proxy AuthPassword: " + InternalStringUtil.maskString(conf.getHttpProxyPassword()));
|
||||
}
|
||||
final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
|
||||
credentialsProvider.setCredentials(new AuthScope(conf.getHttpProxyHost(), conf.getHttpProxyPort()),
|
||||
new UsernamePasswordCredentials(conf.getHttpProxyUser(), conf.getHttpProxyPassword()));
|
||||
clientBuilder.setDefaultCredentialsProvider(credentialsProvider);
|
||||
}
|
||||
}
|
||||
client = clientBuilder.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public twitter4j.http.HttpResponse request(final twitter4j.http.HttpRequest req) throws TwitterException {
|
||||
final HostAddressResolver resolver = FactoryUtils.getHostAddressResolver(conf);
|
||||
final String urlString = req.getURL();
|
||||
final URI urlOrig = ParseUtils.parseURI(urlString);
|
||||
final String host = urlOrig.getHost(), authority = urlOrig.getAuthority();
|
||||
try {
|
||||
HttpRequestBaseHC4 commonsRequest;
|
||||
final String resolvedHost = resolver != null ? resolver.resolve(host) : null;
|
||||
final String resolvedUrl = !isEmpty(resolvedHost) ? urlString.replace("://" + host, "://" + resolvedHost)
|
||||
: urlString;
|
||||
final RequestMethod method = req.getMethod();
|
||||
switch (method) {
|
||||
case GET:
|
||||
commonsRequest = new HttpGetHC4(resolvedUrl);
|
||||
break;
|
||||
case POST:
|
||||
final HttpPostHC4 post = new HttpPostHC4(resolvedUrl);
|
||||
post.setEntity(getAsEntity(req.getParameters()));
|
||||
post.getParams().setBooleanParameter(CoreProtocolPNames.USE_EXPECT_CONTINUE, false);
|
||||
commonsRequest = post;
|
||||
break;
|
||||
case DELETE:
|
||||
commonsRequest = new HttpDeleteHC4(resolvedUrl);
|
||||
break;
|
||||
case HEAD:
|
||||
commonsRequest = new HttpHeadHC4(resolvedUrl);
|
||||
break;
|
||||
case PUT:
|
||||
final HttpPutHC4 put = new HttpPutHC4(resolvedUrl);
|
||||
put.setEntity(getAsEntity(req.getParameters()));
|
||||
commonsRequest = put;
|
||||
break;
|
||||
default:
|
||||
throw new TwitterException("Unsupported request method " + method);
|
||||
}
|
||||
final HttpParams httpParams = commonsRequest.getParams();
|
||||
HttpClientParams.setRedirecting(httpParams, false);
|
||||
final Map<String, String> headers = req.getRequestHeaders();
|
||||
for (final String headerName : headers.keySet()) {
|
||||
commonsRequest.addHeader(headerName, headers.get(headerName));
|
||||
}
|
||||
final Authorization authorization = req.getAuthorization();
|
||||
final String authorizationHeader = authorization != null ? authorization.getAuthorizationHeader(req) : null;
|
||||
if (authorizationHeader != null) {
|
||||
commonsRequest.addHeader(HttpHeaders.AUTHORIZATION, authorizationHeader);
|
||||
}
|
||||
if (resolvedHost != null && !resolvedHost.isEmpty() && !resolvedHost.equals(host)) {
|
||||
commonsRequest.addHeader(HttpHeaders.HOST, authority);
|
||||
}
|
||||
|
||||
final ApacheHttpClientHttpResponseImpl res;
|
||||
try {
|
||||
final HttpContext httpContext = new BasicHttpContextHC4();
|
||||
httpContext.setAttribute(HostResolvedSSLConnectionSocketFactory.HTTP_CONTEXT_KEY_ORIGINAL_HOST, host);
|
||||
res = new ApacheHttpClientHttpResponseImpl(client.execute(commonsRequest, httpContext), conf);
|
||||
} catch (final IllegalStateException e) {
|
||||
throw new TwitterException("Please check your API settings.", e);
|
||||
} catch (final NullPointerException e) {
|
||||
// Bug http://code.google.com/p/android/issues/detail?id=5255
|
||||
throw new TwitterException("Please check your APN settings, make sure not to use WAP APNs.", e);
|
||||
} catch (final OutOfMemoryError e) {
|
||||
// I don't know why OOM thown, but it should be catched.
|
||||
System.gc();
|
||||
throw new TwitterException("Unknown error", e);
|
||||
}
|
||||
final int statusCode = res.getStatusCode();
|
||||
if (statusCode < OK || statusCode > ACCEPTED)
|
||||
throw new TwitterException(res.asString(), req, res);
|
||||
return res;
|
||||
} catch (final IOException e) {
|
||||
// TODO
|
||||
if (resolver instanceof TwidereHostAddressResolver) {
|
||||
final TwidereHostAddressResolver twidereResolver = (TwidereHostAddressResolver) resolver;
|
||||
twidereResolver.removeCachedHost(host);
|
||||
}
|
||||
throw new TwitterException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
Utils.closeSilently(client);
|
||||
}
|
||||
|
||||
private static HttpEntity getAsEntity(final HttpParameter[] params) throws UnsupportedEncodingException {
|
||||
if (params == null) return null;
|
||||
if (!HttpParameter.containsFile(params)) return new HttpParameterFormEntity(params);
|
||||
final MultipartEntityBuilder me = MultipartEntityBuilder.create();
|
||||
for (final HttpParameter param : params) {
|
||||
if (param.isFile()) {
|
||||
final ContentType contentType = ContentType.create(param.getContentType());
|
||||
final ContentBody body;
|
||||
if (param.getFile() != null) {
|
||||
body = new FileBody(param.getFile(), ContentType.create(param.getContentType()));
|
||||
} else {
|
||||
body = new InputStreamBody(param.getFileBody(), contentType, param.getFileName());
|
||||
}
|
||||
me.addPart(param.getName(), body);
|
||||
} else {
|
||||
final ContentType contentType = ContentType.TEXT_PLAIN.withCharset(Consts.UTF_8);
|
||||
final ContentBody body = new StringBody(param.getValue(), contentType);
|
||||
me.addPart(param.getName(), body);
|
||||
}
|
||||
}
|
||||
return me.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 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.net;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.SSLSession;
|
||||
|
||||
public final class HostResolvedHostnameVerifier implements HostnameVerifier {
|
||||
public HostResolvedHostnameVerifier(boolean ignoreSSLError) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean verify(String hostname, SSLSession session) {
|
||||
return true;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 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.net;
|
||||
|
||||
import android.net.SSLCertificateSocketFactory;
|
||||
|
||||
import org.apache.http.conn.util.InetAddressUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import twitter4j.http.HostAddressResolver;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/1/31.
|
||||
*/
|
||||
public class HostResolvedSSLSocketFactory extends SSLSocketFactory {
|
||||
|
||||
private final SSLSocketFactory defaultFactory;
|
||||
private final HostAddressResolver resolver;
|
||||
|
||||
public HostResolvedSSLSocketFactory(HostAddressResolver resolver, boolean ignoreError) {
|
||||
if (ignoreError) {
|
||||
defaultFactory = SSLCertificateSocketFactory.getInsecure(0, null);
|
||||
} else {
|
||||
defaultFactory = SSLCertificateSocketFactory.getDefault(0, null);
|
||||
}
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
return defaultFactory.createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
return defaultFactory.createSocket();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
||||
return defaultFactory.createSocket(host, port, localHost, localPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return defaultFactory.createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
return defaultFactory.createSocket(address, port, localAddress, localPort);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return defaultFactory.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return defaultFactory.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException {
|
||||
return defaultFactory.createSocket(s, host, port, autoClose);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,118 @@
|
|||
/*
|
||||
* Twidere - Twitter client for Android
|
||||
*
|
||||
* Copyright (C) 2012-2015 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.net;
|
||||
|
||||
import org.apache.http.conn.util.InetAddressUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.Inet4Address;
|
||||
import java.net.Inet6Address;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
import twitter4j.http.HostAddressResolver;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/1/31.
|
||||
*/
|
||||
public class HostResolvedSocketFactory extends SocketFactory {
|
||||
|
||||
private final SocketFactory defaultFactory;
|
||||
private final HostAddressResolver resolver;
|
||||
|
||||
public HostResolvedSocketFactory(HostAddressResolver resolver) {
|
||||
defaultFactory = SocketFactory.getDefault();
|
||||
this.resolver = resolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException {
|
||||
final String resolvedHost = resolver.resolve(host);
|
||||
if (resolvedHost != null && !resolvedHost.equals(host)) {
|
||||
if (InetAddressUtils.isIPv6Address(resolvedHost)) {
|
||||
final byte[] resolvedAddress = Inet6Address.getByName(resolvedHost).getAddress();
|
||||
return new Socket(InetAddress.getByAddress(host, resolvedAddress), port);
|
||||
} else if (InetAddressUtils.isIPv4Address(resolvedHost)) {
|
||||
final byte[] resolvedAddress = Inet4Address.getByName(resolvedHost).getAddress();
|
||||
return new Socket(InetAddress.getByAddress(host, resolvedAddress), port);
|
||||
}
|
||||
}
|
||||
return defaultFactory.createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket() throws IOException {
|
||||
return new Socket();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException {
|
||||
final String resolvedHost = resolver.resolve(host);
|
||||
if (resolvedHost != null && !resolvedHost.equals(host)) {
|
||||
if (InetAddressUtils.isIPv6Address(resolvedHost)) {
|
||||
final byte[] resolvedAddress = Inet6Address.getByName(resolvedHost).getAddress();
|
||||
return new Socket(InetAddress.getByAddress(host, resolvedAddress), port);
|
||||
} else if (InetAddressUtils.isIPv4Address(resolvedHost)) {
|
||||
final byte[] resolvedAddress = Inet4Address.getByName(resolvedHost).getAddress();
|
||||
return new Socket(InetAddress.getByAddress(host, resolvedAddress), port);
|
||||
}
|
||||
}
|
||||
return defaultFactory.createSocket(host, port, localHost, localPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
final String hostName = host.getHostName();
|
||||
final String resolvedHost = resolver.resolve(hostName);
|
||||
if (resolvedHost != null && !resolvedHost.equals(hostName)) {
|
||||
if (InetAddressUtils.isIPv6Address(resolvedHost)) {
|
||||
final byte[] resolvedAddress = Inet6Address.getByName(resolvedHost).getAddress();
|
||||
return new Socket(InetAddress.getByAddress(hostName, resolvedAddress), port);
|
||||
} else if (InetAddressUtils.isIPv4Address(resolvedHost)) {
|
||||
final byte[] resolvedAddress = Inet4Address.getByName(resolvedHost).getAddress();
|
||||
return new Socket(InetAddress.getByAddress(hostName, resolvedAddress), port);
|
||||
}
|
||||
}
|
||||
return defaultFactory.createSocket(host, port);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException {
|
||||
final String hostName = address.getHostName();
|
||||
final String resolvedHost = resolver.resolve(hostName);
|
||||
if (resolvedHost != null && !resolvedHost.equals(hostName)) {
|
||||
if (InetAddressUtils.isIPv6Address(resolvedHost)) {
|
||||
final byte[] resolvedAddress = Inet6Address.getByName(resolvedHost).getAddress();
|
||||
return new Socket(InetAddress.getByAddress(hostName, resolvedAddress), port, localAddress, localPort);
|
||||
} else if (InetAddressUtils.isIPv4Address(resolvedHost)) {
|
||||
final byte[] resolvedAddress = Inet4Address.getByName(resolvedHost).getAddress();
|
||||
return new Socket(InetAddress.getByAddress(hostName, resolvedAddress), port, localAddress, localPort);
|
||||
}
|
||||
}
|
||||
return defaultFactory.createSocket(address, port, localAddress, localPort);
|
||||
}
|
||||
|
||||
protected HostAddressResolver getResolver() {
|
||||
return resolver;
|
||||
}
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
package org.mariotaku.twidere.util.net;
|
||||
|
||||
import org.apache.http.Consts;
|
||||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntityHC4;
|
||||
|
||||
import twitter4j.http.HttpParameter;
|
||||
|
||||
import java.io.UnsupportedEncodingException;
|
||||
|
||||
public class HttpParameterFormEntity extends StringEntityHC4 {
|
||||
|
||||
public static final ContentType CONTENT_TYPE = ContentType.APPLICATION_FORM_URLENCODED.withCharset(Consts.UTF_8);
|
||||
|
||||
public HttpParameterFormEntity(final HttpParameter[] params) throws UnsupportedEncodingException {
|
||||
super(HttpParameter.encodeParameters(params), CONTENT_TYPE);
|
||||
}
|
||||
|
||||
}
|
|
@ -17,8 +17,11 @@
|
|||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util.net.ssl;
|
||||
package org.mariotaku.twidere.util.net;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.mariotaku.twidere.app.TwidereApplication;
|
||||
import org.mariotaku.twidere.util.net.OkHttpClientImpl;
|
||||
|
||||
import twitter4j.http.HttpClient;
|
||||
|
@ -29,6 +32,10 @@ import twitter4j.http.HttpClientFactory;
|
|||
* Created by mariotaku on 15/1/22.
|
||||
*/
|
||||
public class OkHttpClientFactory implements HttpClientFactory {
|
||||
public OkHttpClientFactory(Context context) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpClient getInstance(HttpClientConfiguration conf) {
|
||||
return new OkHttpClientImpl(conf);
|
|
@ -20,55 +20,59 @@
|
|||
package org.mariotaku.twidere.util.net;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.squareup.okhttp.Authenticator;
|
||||
import com.squareup.okhttp.Headers;
|
||||
import com.squareup.okhttp.MediaType;
|
||||
import com.squareup.okhttp.OkHttpClient;
|
||||
import com.squareup.okhttp.Protocol;
|
||||
import com.squareup.okhttp.Request;
|
||||
import com.squareup.okhttp.Request.Builder;
|
||||
import com.squareup.okhttp.RequestBody;
|
||||
import com.squareup.okhttp.Response;
|
||||
|
||||
import org.mariotaku.twidere.TwidereConstants;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Proxy;
|
||||
import java.net.Proxy.Type;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.UUID;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
|
||||
import twitter4j.TwitterException;
|
||||
import twitter4j.auth.Authorization;
|
||||
import twitter4j.http.HostAddressResolver;
|
||||
import twitter4j.http.HttpClient;
|
||||
import twitter4j.http.HttpClientConfiguration;
|
||||
import twitter4j.http.HttpParameter;
|
||||
import twitter4j.http.HttpRequest;
|
||||
import twitter4j.http.HttpResponse;
|
||||
import twitter4j.http.RequestMethod;
|
||||
|
||||
/**
|
||||
* Created by mariotaku on 15/1/22.
|
||||
*/
|
||||
public class OkHttpClientImpl implements HttpClient, TwidereConstants {
|
||||
|
||||
public static final MediaType APPLICATION_FORM_URLENCODED = MediaType.parse("application/x-www-form-urlencoded; charset=UTF-8");
|
||||
private final HttpClientConfiguration conf;
|
||||
private final OkHttpClient client;
|
||||
private final HostAddressResolver resolver;
|
||||
|
||||
public OkHttpClientImpl(HttpClientConfiguration conf) {
|
||||
this.conf = conf;
|
||||
this.resolver = conf.getHostAddressResolverFactory().getInstance(conf);
|
||||
this.client = createHttpClient(conf);
|
||||
}
|
||||
|
||||
private OkHttpClient createHttpClient(HttpClientConfiguration conf) {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
if (conf.isSSLErrorIgnored()) {
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpResponse request(HttpRequest req) throws TwitterException {
|
||||
final Builder builder = new Builder();
|
||||
|
@ -82,59 +86,34 @@ public class OkHttpClientImpl implements HttpClient, TwidereConstants {
|
|||
builder.header("Authorization", authHeader);
|
||||
}
|
||||
}
|
||||
final String url;
|
||||
try {
|
||||
switch (req.getMethod()) {
|
||||
case GET: {
|
||||
url = getUrl(req);
|
||||
builder.get();
|
||||
break;
|
||||
}
|
||||
case POST: {
|
||||
url = req.getURL();
|
||||
builder.post(getRequestBody(req.getParameters()));
|
||||
break;
|
||||
}
|
||||
case DELETE: {
|
||||
url = getUrl(req);
|
||||
builder.delete();
|
||||
break;
|
||||
}
|
||||
case HEAD: {
|
||||
url = getUrl(req);
|
||||
builder.head();
|
||||
break;
|
||||
}
|
||||
case PUT: {
|
||||
url = req.getURL();
|
||||
builder.put(getRequestBody(req.getParameters()));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
builder.url(url);
|
||||
setupRequestBuilder(builder, req);
|
||||
final Response response = client.newCall(builder.build()).execute();
|
||||
Log.d(TwidereConstants.LOGTAG, String.format("OkHttpClient finished a request to %s with %s protocol", url, response.protocol().name()));
|
||||
return new OkHttpResponse(conf, null, response);
|
||||
} catch (IOException e) {
|
||||
throw new TwitterException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getUrl(HttpRequest req) {
|
||||
final Uri.Builder uri = Uri.parse(req.getURL()).buildUpon();
|
||||
for (HttpParameter param : req.getParameters()) {
|
||||
uri.appendQueryParameter(param.getName(), param.getValue());
|
||||
}
|
||||
return uri.build().toString();
|
||||
@Override
|
||||
public void shutdown() {
|
||||
|
||||
}
|
||||
|
||||
public static final MediaType APPLICATION_FORM_URLENCODED = MediaType.parse("application/x-www-form-urlencoded; charset=UTF-8");
|
||||
public static final MediaType MULTIPART_FORM_DATA = MediaType.parse("multipart/form-data; charset=UTF-8");
|
||||
private OkHttpClient createHttpClient(HttpClientConfiguration conf) {
|
||||
final OkHttpClient client = new OkHttpClient();
|
||||
final boolean ignoreSSLError = conf.isSSLErrorIgnored();
|
||||
client.setHostnameVerifier(new HostResolvedHostnameVerifier(ignoreSSLError));
|
||||
client.setSslSocketFactory(new HostResolvedSSLSocketFactory(resolver, ignoreSSLError));
|
||||
client.setSocketFactory(new HostResolvedSocketFactory(resolver));
|
||||
if (conf.isProxyConfigured()) {
|
||||
client.setProxy(new Proxy(Type.HTTP, InetSocketAddress.createUnresolved(conf.getHttpProxyHost(),
|
||||
conf.getHttpProxyPort())));
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
private RequestBody getRequestBody(HttpParameter[] params) {
|
||||
private RequestBody getRequestBody(HttpParameter[] params) throws IOException {
|
||||
if (params == null) return null;
|
||||
if (!HttpParameter.containsFile(params)) {
|
||||
return RequestBody.create(APPLICATION_FORM_URLENCODED, HttpParameter.encodeParameters(params));
|
||||
|
@ -148,12 +127,71 @@ public class OkHttpClientImpl implements HttpClient, TwidereConstants {
|
|||
return RequestBody.create(MediaType.parse(param.getContentType()), param.getFile());
|
||||
}
|
||||
}
|
||||
return null;
|
||||
String boundary = String.format("----%s", UUID.randomUUID().toString());
|
||||
final MediaType mediaType = MediaType.parse("multipart/form-data; boundary=" + boundary);
|
||||
boundary = "--" + boundary;
|
||||
final ByteArrayOutputStream os = new ByteArrayOutputStream();
|
||||
for (final HttpParameter param : params) {
|
||||
os.write(String.format("%s\r\n", boundary).getBytes("UTF-8"));
|
||||
if (param.isFile()) {
|
||||
os.write(String.format("Content-Disposition: form-data; name=\"%s\"; filename=\"%s\"\r\n", param.getName(), param.getFileName()).getBytes("UTF-8"));
|
||||
os.write(String.format("Content-Type: %s\r\n\r\n", param.getContentType()).getBytes("UTF-8"));
|
||||
final BufferedInputStream in = new BufferedInputStream(param.hasFileBody() ?
|
||||
param.getFileBody() : new FileInputStream(param.getFile()));
|
||||
byte[] buff = new byte[8192];
|
||||
while (in.read(buff) != -1) {
|
||||
os.write(buff);
|
||||
}
|
||||
in.close();
|
||||
} else {
|
||||
os.write(String.format("Content-Disposition: form-data; name=\"%s\"\r\n", param.getName()).getBytes("UTF-8"));
|
||||
os.write("Content-Type: text/plain; charset=UTF-8\r\n\r\n".getBytes("UTF-8"));
|
||||
os.write(param.getValue().getBytes("UTF-8"));
|
||||
}
|
||||
os.write("\r\n".getBytes("UTF-8"));
|
||||
}
|
||||
os.write(String.format("%s--\r\n", boundary).getBytes("UTF-8"));
|
||||
return RequestBody.create(mediaType, os.toByteArray());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void shutdown() {
|
||||
|
||||
private void setupRequestBuilder(Builder builder, HttpRequest req) throws IOException {
|
||||
final Uri.Builder uriBuilder = Uri.parse(req.getURL()).buildUpon();
|
||||
final RequestMethod method = req.getMethod();
|
||||
if (method != RequestMethod.POST && method != RequestMethod.PUT) {
|
||||
final HttpParameter[] parameters = req.getParameters();
|
||||
if (parameters != null) {
|
||||
for (HttpParameter param : parameters) {
|
||||
uriBuilder.appendQueryParameter(param.getName(), param.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
final Uri uri = uriBuilder.build();
|
||||
switch (req.getMethod()) {
|
||||
case GET: {
|
||||
builder.get();
|
||||
break;
|
||||
}
|
||||
case POST: {
|
||||
builder.post(getRequestBody(req.getParameters()));
|
||||
break;
|
||||
}
|
||||
case DELETE: {
|
||||
builder.delete();
|
||||
break;
|
||||
}
|
||||
case HEAD: {
|
||||
builder.head();
|
||||
break;
|
||||
}
|
||||
case PUT: {
|
||||
builder.put(getRequestBody(req.getParameters()));
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
builder.url(uri.toString());
|
||||
}
|
||||
|
||||
private static class OkHttpResponse extends HttpResponse {
|
||||
|
|
|
@ -23,7 +23,7 @@ import android.content.Context;
|
|||
import android.content.SharedPreferences;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.http.conn.util.InetAddressUtilsHC4;
|
||||
import org.apache.http.conn.util.InetAddressUtils;
|
||||
import org.mariotaku.twidere.Constants;
|
||||
import org.mariotaku.twidere.util.HostsFileParser;
|
||||
import org.mariotaku.twidere.util.Utils;
|
||||
|
@ -191,7 +191,7 @@ public class TwidereHostAddressResolver implements Constants, HostAddressResolve
|
|||
|
||||
private static boolean isValidIpAddress(final String address) {
|
||||
if (isEmpty(address)) return false;
|
||||
return InetAddressUtilsHC4.isIPv4Address(address) || InetAddressUtilsHC4.isIPv6Address(address);
|
||||
return InetAddressUtils.isIPv4Address(address) || InetAddressUtils.isIPv6Address(address);
|
||||
}
|
||||
|
||||
private static class HostCache extends LinkedHashMap<String, String> {
|
||||
|
|
|
@ -1,347 +0,0 @@
|
|||
package org.mariotaku.twidere.util.net.ssl;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||
import org.apache.http.conn.util.InetAddressUtilsHC4;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateParsingException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
public abstract class AbstractCheckSignatureVerifier implements X509HostnameVerifier {
|
||||
|
||||
/**
|
||||
* This contains a list of 2nd-level domains that aren't allowed to have
|
||||
* wildcards when combined with country-codes. For example: [*.co.uk].
|
||||
* <p/>
|
||||
* The [*.co.uk] problem is an interesting one. Should we just hope that
|
||||
* CA's would never foolishly allow such a certificate to happen? Looks like
|
||||
* we're the only implementation guarding against this. Firefox, Curl, Sun
|
||||
* Java 1.4, 5, 6 don't bother with this check.
|
||||
*/
|
||||
private final static String[] BAD_COUNTRY_2LDS = { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info",
|
||||
"lg", "ne", "net", "or", "org" };
|
||||
|
||||
static {
|
||||
// Just in case developer forgot to manually sort the array. :-)
|
||||
Arrays.sort(BAD_COUNTRY_2LDS);
|
||||
}
|
||||
|
||||
private final static String TAG = "HttpClient";
|
||||
|
||||
@Override
|
||||
public final boolean verify(final String host, final SSLSession session) {
|
||||
try {
|
||||
final Certificate[] certs = session.getPeerCertificates();
|
||||
final X509Certificate x509 = (X509Certificate) certs[0];
|
||||
verify(host, x509);
|
||||
return true;
|
||||
} catch (final SSLException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void verify(final String host, final SSLSocket ssl) throws IOException {
|
||||
if (host == null) throw new NullPointerException("host to verify is null");
|
||||
|
||||
SSLSession session = ssl.getSession();
|
||||
if (session == null) {
|
||||
// In our experience this only happens under IBM 1.4.x when
|
||||
// spurious (unrelated) certificates show up in the server'
|
||||
// chain. Hopefully this will unearth the real problem:
|
||||
final InputStream in = ssl.getInputStream();
|
||||
in.available();
|
||||
/*
|
||||
* If you're looking at the 2 lines of code above because you're
|
||||
* running into a problem, you probably have two options:
|
||||
*
|
||||
* #1. Clean up the certificate chain that your server is presenting
|
||||
* (e.g. edit "/etc/apache2/server.crt" or wherever it is your
|
||||
* server's certificate chain is defined).
|
||||
*
|
||||
* OR
|
||||
*
|
||||
* #2. Upgrade to an IBM 1.5.x or greater JVM, or switch to a
|
||||
* non-IBM JVM.
|
||||
*/
|
||||
|
||||
// If ssl.getInputStream().available() didn't cause an
|
||||
// exception, maybe at least now the session is available?
|
||||
session = ssl.getSession();
|
||||
if (session == null) {
|
||||
// If it's still null, probably a startHandshake() will
|
||||
// unearth the real problem.
|
||||
ssl.startHandshake();
|
||||
|
||||
// Okay, if we still haven't managed to cause an exception,
|
||||
// might as well go for the NPE. Or maybe we're okay now?
|
||||
session = ssl.getSession();
|
||||
}
|
||||
}
|
||||
|
||||
final Certificate[] certs = session.getPeerCertificates();
|
||||
final X509Certificate x509 = (X509Certificate) certs[0];
|
||||
verify(host, x509);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void verify(final String host, final String[] cns, final String[] subjectAlts) throws SSLException {
|
||||
verify(host, cns, subjectAlts, null);
|
||||
}
|
||||
|
||||
public abstract void verify(final String host, final String[] cns, final String[] subjectAlts,
|
||||
final X509Certificate cert) throws SSLException;
|
||||
|
||||
@Override
|
||||
public final void verify(final String host, final X509Certificate cert) throws SSLException {
|
||||
final String[] cns = getCNs(cert);
|
||||
final String[] subjectAlts = getSubjectAlts(cert, host);
|
||||
verify(host, cns, subjectAlts, cert);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated (4.3.1) should not be a part of public APIs.
|
||||
*/
|
||||
@Deprecated
|
||||
public static boolean acceptableCountryWildcard(final String cn) {
|
||||
final String parts[] = cn.split("\\.");// it's
|
||||
// not an attempt to wildcard a 2TLD within a country code
|
||||
if (parts.length != 3 || parts[2].length() != 2) return true;
|
||||
return Arrays.binarySearch(BAD_COUNTRY_2LDS, parts[1]) < 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Counts the number of dots "." in a string.
|
||||
*
|
||||
* @param s string to count dots from
|
||||
* @return number of dots
|
||||
*/
|
||||
public static int countDots(final String s) {
|
||||
int count = 0;
|
||||
for (int i = 0; i < s.length(); i++) {
|
||||
if (s.charAt(i) == '.') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
public static String[] getCNs(final X509Certificate cert) {
|
||||
final LinkedList<String> cnList = new LinkedList<String>();
|
||||
/*
|
||||
* Sebastian Hauer's original StrictSSLProtocolSocketFactory used
|
||||
* getName() and had the following comment:
|
||||
*
|
||||
* Parses a X.500 distinguished name for the value of the "Common Name"
|
||||
* field. This is done a bit sloppy right now and should probably be
|
||||
* done a bit more according to <code>RFC 2253</code>.
|
||||
*
|
||||
* I've noticed that toString() seems to do a better job than getName()
|
||||
* on these X500Principal objects, so I'm hoping that addresses
|
||||
* Sebastian's concern.
|
||||
*
|
||||
* For example, getName() gives me this:
|
||||
* 1.2.840.113549.1.9.1=#16166a756c6975736461766965734063756362632e636f6d
|
||||
*
|
||||
* whereas toString() gives me this: EMAILADDRESS=juliusdavies@cucbc.com
|
||||
*
|
||||
* Looks like toString() even works with non-ascii domain names! I
|
||||
* tested it with "花子.co.jp" and it worked fine.
|
||||
*/
|
||||
|
||||
final String subjectPrincipal = cert.getSubjectX500Principal().toString();
|
||||
final StringTokenizer st = new StringTokenizer(subjectPrincipal, ",+");
|
||||
while (st.hasMoreTokens()) {
|
||||
final String tok = st.nextToken().trim();
|
||||
if (tok.length() > 3) {
|
||||
if (tok.substring(0, 3).equalsIgnoreCase("CN=")) {
|
||||
cnList.add(tok.substring(3));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!cnList.isEmpty()) {
|
||||
final String[] cns = new String[cnList.size()];
|
||||
cnList.toArray(cns);
|
||||
return cns;
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the array of SubjectAlt DNS names from an X509Certificate.
|
||||
* Returns null if there aren't any.
|
||||
* <p/>
|
||||
* Note: Java doesn't appear able to extract international characters from
|
||||
* the SubjectAlts. It can only extract international characters from the CN
|
||||
* field.
|
||||
* <p/>
|
||||
* (Or maybe the version of OpenSSL I'm using to test isn't storing the
|
||||
* international characters correctly in the SubjectAlts?).
|
||||
*
|
||||
* @param cert X509Certificate
|
||||
* @return Array of SubjectALT DNS names stored in the certificate.
|
||||
*/
|
||||
public static String[] getDNSSubjectAlts(final X509Certificate cert) {
|
||||
return getSubjectAlts(cert, null);
|
||||
}
|
||||
|
||||
public static final boolean verify(final String host, final String[] cns, final String[] subjectAlts,
|
||||
final boolean strictWithSubDomains) {
|
||||
|
||||
// Build the list of names we're going to check. Our DEFAULT and
|
||||
// STRICT implementations of the HostnameVerifier only use the
|
||||
// first CN provided. All other CNs are ignored.
|
||||
// (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way).
|
||||
final LinkedList<String> names = new LinkedList<String>();
|
||||
if (cns != null && cns.length > 0 && cns[0] != null) {
|
||||
names.add(cns[0]);
|
||||
}
|
||||
if (subjectAlts != null) {
|
||||
for (final String subjectAlt : subjectAlts) {
|
||||
if (subjectAlt != null) {
|
||||
names.add(subjectAlt);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (names.isEmpty()) return false;
|
||||
|
||||
// StringBuilder for building the error message.
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
|
||||
// We're can be case-insensitive when comparing the host we used to
|
||||
// establish the socket to the hostname in the certificate.
|
||||
final String hostName = normaliseIPv6Address(host.trim().toLowerCase(Locale.US));
|
||||
boolean match = false;
|
||||
for (final Iterator<String> it = names.iterator(); it.hasNext();) {
|
||||
// Don't trim the CN, though!
|
||||
String cn = it.next();
|
||||
cn = cn.toLowerCase(Locale.US);
|
||||
// Store CN in StringBuilder in case we need to report an error.
|
||||
buf.append(" <");
|
||||
buf.append(cn);
|
||||
buf.append('>');
|
||||
if (it.hasNext()) {
|
||||
buf.append(" OR");
|
||||
}
|
||||
|
||||
// The CN better have at least two dots if it wants wildcard
|
||||
// action. It also can't be [*.co.uk] or [*.co.jp] or
|
||||
// [*.org.uk], etc...
|
||||
final String parts[] = cn.split("\\.");
|
||||
final boolean doWildcard = parts.length >= 3 && parts[0].endsWith("*") && validCountryWildcard(cn)
|
||||
&& !isIPAddress(host);
|
||||
|
||||
if (doWildcard) {
|
||||
final String firstpart = parts[0];
|
||||
if (firstpart.length() > 1) { // e.g. server*
|
||||
// e.g. server
|
||||
final String prefix = firstpart.substring(0, firstpart.length() - 1);
|
||||
// skip wildcard part from cn
|
||||
final String suffix = cn.substring(firstpart.length());// skip
|
||||
// wildcard part from host
|
||||
final String hostSuffix = hostName.substring(prefix.length());
|
||||
match = hostName.startsWith(prefix) && hostSuffix.endsWith(suffix);
|
||||
} else {
|
||||
match = hostName.endsWith(cn.substring(1));
|
||||
}
|
||||
if (match && strictWithSubDomains) {
|
||||
// If we're in strict mode, then [*.foo.com] is not
|
||||
// allowed to match [a.b.foo.com]
|
||||
match = countDots(hostName) == countDots(cn);
|
||||
}
|
||||
} else {
|
||||
match = hostName.equals(normaliseIPv6Address(cn));
|
||||
}
|
||||
if (match) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return match;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts the array of SubjectAlt DNS or IP names from an X509Certificate.
|
||||
* Returns null if there aren't any.
|
||||
*
|
||||
* @param cert X509Certificate
|
||||
* @param hostname
|
||||
* @return Array of SubjectALT DNS or IP names stored in the certificate.
|
||||
*/
|
||||
private static String[] getSubjectAlts(final X509Certificate cert, final String hostname) {
|
||||
final int subjectType;
|
||||
if (isIPAddress(hostname)) {
|
||||
subjectType = 7;
|
||||
} else {
|
||||
subjectType = 2;
|
||||
}
|
||||
|
||||
final LinkedList<String> subjectAltList = new LinkedList<String>();
|
||||
Collection<List<?>> c = null;
|
||||
try {
|
||||
c = cert.getSubjectAlternativeNames();
|
||||
} catch (final CertificateParsingException cpe) {
|
||||
}
|
||||
if (c != null) {
|
||||
for (final List<?> aC : c) {
|
||||
final List<?> list = aC;
|
||||
final int type = ((Integer) list.get(0)).intValue();
|
||||
if (type == subjectType) {
|
||||
final String s = (String) list.get(1);
|
||||
subjectAltList.add(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!subjectAltList.isEmpty()) {
|
||||
final String[] subjectAlts = new String[subjectAltList.size()];
|
||||
subjectAltList.toArray(subjectAlts);
|
||||
return subjectAlts;
|
||||
} else
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isIPAddress(final String hostname) {
|
||||
return hostname != null
|
||||
&& (InetAddressUtilsHC4.isIPv4Address(hostname) || InetAddressUtilsHC4.isIPv6Address(hostname));
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if hostname is IPv6, and if so, convert to standard format.
|
||||
*/
|
||||
private static String normaliseIPv6Address(final String hostname) {
|
||||
if (hostname == null || !InetAddressUtilsHC4.isIPv6Address(hostname)) return hostname;
|
||||
try {
|
||||
final InetAddress inetAddress = InetAddress.getByName(hostname);
|
||||
return inetAddress.getHostAddress();
|
||||
} catch (final UnknownHostException uhe) { // Should not happen, because
|
||||
// we check for IPv6 address
|
||||
// above
|
||||
Log.e(TAG, "Unexpected error converting " + hostname, uhe);
|
||||
return hostname;
|
||||
}
|
||||
}
|
||||
|
||||
static boolean validCountryWildcard(final String cn) {
|
||||
final String parts[] = cn.split("\\.");
|
||||
// it's not an attempt to wildcard a 2TLD within a country code
|
||||
if (parts.length != 3 || parts[2].length() != 2) return true;
|
||||
return Arrays.binarySearch(BAD_COUNTRY_2LDS, parts[1]) < 0;
|
||||
}
|
||||
}
|
|
@ -1,194 +0,0 @@
|
|||
/*
|
||||
* ====================================================================
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you 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.
|
||||
* ====================================================================
|
||||
*
|
||||
* This software consists of voluntary contributions made by many
|
||||
* individuals on behalf of the Apache Software Foundation. For more
|
||||
* information on the Apache Software Foundation, please see
|
||||
* <http://www.apache.org/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.mariotaku.twidere.util.net.ssl;
|
||||
|
||||
import android.net.SSLCertificateSocketFactory;
|
||||
import android.os.Build;
|
||||
import android.util.Log;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.annotation.ThreadSafe;
|
||||
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
import org.apache.http.util.Args;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
|
||||
import javax.net.SocketFactory;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
|
||||
@ThreadSafe
|
||||
public class HostResolvedSSLConnectionSocketFactory implements LayeredConnectionSocketFactory {
|
||||
|
||||
private static final String TAG = "HttpClient";
|
||||
|
||||
public static final String HTTP_CONTEXT_KEY_ORIGINAL_HOST = "original_host";
|
||||
|
||||
private final javax.net.ssl.SSLSocketFactory socketfactory;
|
||||
|
||||
private final X509HostnameVerifier hostnameVerifier;
|
||||
|
||||
private final String[] supportedProtocols;
|
||||
|
||||
private final String[] supportedCipherSuites;
|
||||
|
||||
public HostResolvedSSLConnectionSocketFactory(final javax.net.ssl.SSLSocketFactory socketfactory,
|
||||
final String[] supportedProtocols, final String[] supportedCipherSuites,
|
||||
final X509HostnameVerifier hostnameVerifier) {
|
||||
this.socketfactory = Args.notNull(socketfactory, "SSL socket factory");
|
||||
this.supportedProtocols = supportedProtocols;
|
||||
this.supportedCipherSuites = supportedCipherSuites;
|
||||
this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier
|
||||
: SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER;
|
||||
}
|
||||
|
||||
public HostResolvedSSLConnectionSocketFactory(final javax.net.ssl.SSLSocketFactory socketfactory,
|
||||
final X509HostnameVerifier hostnameVerifier) {
|
||||
this(socketfactory, null, null, hostnameVerifier);
|
||||
}
|
||||
|
||||
public HostResolvedSSLConnectionSocketFactory(final SSLContext sslContext) {
|
||||
this(sslContext, SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER);
|
||||
}
|
||||
|
||||
public HostResolvedSSLConnectionSocketFactory(final SSLContext sslContext, final String[] supportedProtocols,
|
||||
final String[] supportedCipherSuites, final X509HostnameVerifier hostnameVerifier) {
|
||||
this(Args.notNull(sslContext, "SSL context").getSocketFactory(), supportedProtocols, supportedCipherSuites,
|
||||
hostnameVerifier);
|
||||
}
|
||||
|
||||
public HostResolvedSSLConnectionSocketFactory(final SSLContext sslContext,
|
||||
final X509HostnameVerifier hostnameVerifier) {
|
||||
this(Args.notNull(sslContext, "SSL context").getSocketFactory(), null, null, hostnameVerifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket connectSocket(final int connectTimeout, final Socket socket, final HttpHost host,
|
||||
final InetSocketAddress remoteAddress, final InetSocketAddress localAddress, final HttpContext context)
|
||||
throws IOException {
|
||||
Args.notNull(host, "HTTP host");
|
||||
Args.notNull(remoteAddress, "Remote address");
|
||||
final Socket sock = socket != null ? socket : createSocket(context);
|
||||
if (localAddress != null) {
|
||||
sock.bind(localAddress);
|
||||
}
|
||||
try {
|
||||
sock.connect(remoteAddress, connectTimeout);
|
||||
} catch (final IOException ex) {
|
||||
try {
|
||||
sock.close();
|
||||
} catch (final IOException ignore) {
|
||||
}
|
||||
throw ex;
|
||||
}
|
||||
// Setup SSL layering if necessary
|
||||
if (sock instanceof SSLSocket) {
|
||||
final SSLSocket sslsock = (SSLSocket) sock;
|
||||
sslsock.startHandshake();
|
||||
verifyHostname(sslsock, host.getHostName(), context);
|
||||
return sock;
|
||||
} else
|
||||
return createLayeredSocket(sock, host.getHostName(), remoteAddress.getPort(), context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createLayeredSocket(final Socket socket, final String target, final int port,
|
||||
final HttpContext context) throws IOException {
|
||||
final SSLSocket sslsock = (SSLSocket) socketfactory.createSocket(socket, target, port, true);
|
||||
if (supportedProtocols != null) {
|
||||
sslsock.setEnabledProtocols(supportedProtocols);
|
||||
}
|
||||
if (supportedCipherSuites != null) {
|
||||
sslsock.setEnabledCipherSuites(supportedCipherSuites);
|
||||
}
|
||||
prepareSocket(sslsock);
|
||||
|
||||
// Android specific code to enable SNI
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
|
||||
|
||||
if (socketfactory instanceof SSLCertificateSocketFactory) {
|
||||
if (Log.isLoggable(TAG, Log.DEBUG)) {
|
||||
Log.d(TAG, "Enabling SNI for " + target);
|
||||
}
|
||||
((SSLCertificateSocketFactory) socketfactory).setHostname(sslsock, target);
|
||||
}
|
||||
}
|
||||
// End of Android specific code
|
||||
|
||||
sslsock.startHandshake();
|
||||
verifyHostname(sslsock, target, context);
|
||||
return sslsock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(final HttpContext context) throws IOException {
|
||||
return SocketFactory.getDefault().createSocket();
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs any custom initialization for a newly created SSLSocket (before
|
||||
* the SSL handshake happens).
|
||||
*
|
||||
* The default implementation is a no-op, but could be overridden to, e.g.,
|
||||
* call {@link javax.net.ssl.SSLSocket#setEnabledCipherSuites(String[])}.
|
||||
*/
|
||||
protected void prepareSocket(final SSLSocket socket) throws IOException {
|
||||
}
|
||||
|
||||
private String getHostname(final String hostname, final HttpContext context) {
|
||||
if (context == null) return hostname;
|
||||
final Object attr = context.getAttribute(HTTP_CONTEXT_KEY_ORIGINAL_HOST);
|
||||
if (attr instanceof String) return (String) attr;
|
||||
return hostname;
|
||||
}
|
||||
|
||||
private void verifyHostname(final SSLSocket sslsock, final String hostname, final HttpContext context)
|
||||
throws IOException {
|
||||
try {
|
||||
hostnameVerifier.verify(getHostname(hostname, context), sslsock);
|
||||
// verifyHostName() didn't blowup - good!
|
||||
} catch (final IOException iox) {
|
||||
// close the socket before re-throwing the exception
|
||||
try {
|
||||
sslsock.close();
|
||||
} catch (final Exception x) { /* ignore */
|
||||
}
|
||||
throw iox;
|
||||
}
|
||||
}
|
||||
|
||||
X509HostnameVerifier getHostnameVerifier() {
|
||||
return hostnameVerifier;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,20 +0,0 @@
|
|||
package org.mariotaku.twidere.util.net.ssl;
|
||||
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public final class TrustAllX509TrustManager implements X509TrustManager {
|
||||
@Override
|
||||
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void checkServerTrusted(final X509Certificate[] chain, final String authType) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return new X509Certificate[0];
|
||||
}
|
||||
}
|
|
@ -1,34 +0,0 @@
|
|||
package org.mariotaku.twidere.util.net.ssl;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.net.ssl.SSLException;
|
||||
|
||||
public class TwidereHostnameVerifier extends AbstractCheckSignatureVerifier {
|
||||
|
||||
private final Context context;
|
||||
private final boolean ignoreSSLErrors;
|
||||
|
||||
public TwidereHostnameVerifier(final Context context, final boolean ignoreSSLErrors)
|
||||
throws NoSuchAlgorithmException, KeyStoreException {
|
||||
this.context = context;
|
||||
this.ignoreSSLErrors = ignoreSSLErrors;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void verify(final String host, final String[] cns, final String[] subjectAlts, final X509Certificate cert)
|
||||
throws SSLException {
|
||||
if (ignoreSSLErrors) return;
|
||||
if (!checkCert(cert)) throw new SSLException(String.format("Untrusted cert %s", cert));
|
||||
if (!verify(host, cns, subjectAlts, false)) throw new SSLException(String.format("Unable to verify %s", host));
|
||||
}
|
||||
|
||||
private boolean checkCert(final X509Certificate cert) {
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,66 +0,0 @@
|
|||
package org.mariotaku.twidere.util.net.ssl;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.apache.http.HttpHost;
|
||||
import org.apache.http.conn.socket.LayeredConnectionSocketFactory;
|
||||
import org.apache.http.conn.ssl.SSLInitializationException;
|
||||
import org.apache.http.conn.ssl.X509HostnameVerifier;
|
||||
import org.apache.http.protocol.HttpContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.Socket;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.KeyManagementException;
|
||||
import java.security.KeyStoreException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.TrustManager;
|
||||
|
||||
public final class TwidereSSLSocketFactory implements LayeredConnectionSocketFactory {
|
||||
|
||||
private final Context context;
|
||||
private final boolean ignoreSSLErrors;
|
||||
private final HostResolvedSSLConnectionSocketFactory delegated;
|
||||
|
||||
private TwidereSSLSocketFactory(final Context context, final boolean ignoreSSLErrors)
|
||||
throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException {
|
||||
this.context = context;
|
||||
this.ignoreSSLErrors = ignoreSSLErrors;
|
||||
final TrustManager[] tm = { new TrustAllX509TrustManager() };
|
||||
final SSLContext sslContext = SSLContext.getInstance("TLS");
|
||||
sslContext.init(null, tm, null);
|
||||
final X509HostnameVerifier hostnameVerifier = new TwidereHostnameVerifier(context, ignoreSSLErrors);
|
||||
delegated = new HostResolvedSSLConnectionSocketFactory(sslContext, hostnameVerifier);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket connectSocket(final int connectTimeout, final Socket socket, final HttpHost host,
|
||||
final InetSocketAddress remoteAddress, final InetSocketAddress localAddress, final HttpContext httpContext)
|
||||
throws IOException {
|
||||
return delegated.connectSocket(connectTimeout, socket, host, remoteAddress, localAddress, httpContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createLayeredSocket(final Socket socket, final String target, final int port,
|
||||
final HttpContext httpContext) throws IOException {
|
||||
return delegated.createLayeredSocket(socket, target, port, httpContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(final HttpContext httpContext) throws IOException {
|
||||
return delegated.createSocket(httpContext);
|
||||
}
|
||||
|
||||
public static LayeredConnectionSocketFactory getSocketFactory(final Context context, final boolean ignoreSSLErrors)
|
||||
throws SSLInitializationException {
|
||||
try {
|
||||
return new TwidereSSLSocketFactory(context, ignoreSSLErrors);
|
||||
} catch (final GeneralSecurityException e) {
|
||||
throw new SSLInitializationException("Cannot create socket factory", e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Before Width: | Height: | Size: 4.7 KiB After Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 2.7 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 7.6 KiB After Width: | Height: | Size: 6.2 KiB |
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 10 KiB |
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 14 KiB |
|
@ -27,11 +27,6 @@
|
|||
android:minWidth="@dimen/compose_min_width"
|
||||
android:orientation="vertical">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/compose_actionbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?actionBarSize"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/compose_content"
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -183,31 +183,16 @@
|
|||
<item name="messageBubbleColor">@color/message_bubble_color_light</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Twidere.Dark.Compose">
|
||||
<style name="Theme.Twidere.Dark.Compose" parent="Theme.Compat.Base.Dialog">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowActionModeOverlay">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@drawable/dialog_full_holo_dark</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
|
||||
<item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
|
||||
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
|
||||
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
|
||||
</style>
|
||||
|
||||
<style name="Theme.Twidere.Light.Compose">
|
||||
<style name="Theme.Twidere.Light.Compose" parent="Theme.Compat.Base.Light.Dialog">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowActionModeOverlay">false</item>
|
||||
<item name="android:windowNoTitle">true</item>
|
||||
<item name="android:windowBackground">@drawable/dialog_full_holo_light</item>
|
||||
<item name="android:windowIsFloating">true</item>
|
||||
<item name="android:windowContentOverlay">@null</item>
|
||||
<item name="android:windowAnimationStyle">@android:style/Animation.Dialog</item>
|
||||
<item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
|
||||
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
|
||||
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
|
||||
|
||||
</style>
|
||||
|
||||
<style name="Theme.Twidere.Light.QuickSearchBar" parent="Theme.Twidere.Light.Dialog">
|
||||
|
|