commit for sync

This commit is contained in:
Mariotaku Lee 2015-02-03 13:22:02 +08:00
parent 2d2c94c321
commit 21cad7ebb8
35 changed files with 772 additions and 1391 deletions

View File

@ -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

View File

@ -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')

View File

@ -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();
}
}
}

View File

@ -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();
}

View File

@ -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 {
}

View File

@ -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 + "}";
}
}
}

View File

@ -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[]{};
}
}
}

View File

@ -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'

View File

@ -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>

View File

@ -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();
}

View File

@ -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);

View File

@ -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 {

View File

@ -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);

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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 {

View File

@ -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> {

View File

@ -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 "&#x82b1;&#x5b50;.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;
}
}

View File

@ -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;
}
}

View File

@ -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];
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -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"

View File

@ -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">