diff --git a/Clover/app/src/main/java/com/android/volley/NetworkDispatcher.java b/Clover/app/src/main/java/com/android/volley/NetworkDispatcher.java index a654ead5..9c1c2e34 100644 --- a/Clover/app/src/main/java/com/android/volley/NetworkDispatcher.java +++ b/Clover/app/src/main/java/com/android/volley/NetworkDispatcher.java @@ -81,8 +81,8 @@ public class NetworkDispatcher extends Thread { @Override public void run() { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - Request request; while (true) { + Request request; try { // Take a request from the queue. request = mQueue.take(); diff --git a/Clover/app/src/main/java/com/android/volley/Request.java b/Clover/app/src/main/java/com/android/volley/Request.java index 53093e68..98329527 100644 --- a/Clover/app/src/main/java/com/android/volley/Request.java +++ b/Clover/app/src/main/java/com/android/volley/Request.java @@ -163,6 +163,13 @@ public abstract class Request implements Comparable> { return mTag; } + /** + * @return this request's {@link com.android.volley.Response.ErrorListener}. + */ + public Response.ErrorListener getErrorListener() { + return mErrorListener; + } + /** * @return A tag for use with {@link TrafficStats#setThreadStatsTag(int)} */ @@ -420,6 +427,9 @@ public abstract class Request implements Comparable> { return DEFAULT_PARAMS_ENCODING; } + /** + * Returns the content type of the POST or PUT body. + */ public String getBodyContentType() { return "application/x-www-form-urlencoded; charset=" + getParamsEncoding(); } @@ -427,6 +437,10 @@ public abstract class Request implements Comparable> { /** * Returns the raw POST or PUT body to be sent. * + *

By default, the body consists of the request parameters in + * application/x-www-form-urlencoded format. When overriding this method, consider overriding + * {@link #getBodyContentType()} as well to match the new body format. + * * @throws AuthFailureError in the event of auth failure */ public byte[] getBody() throws AuthFailureError { diff --git a/Clover/app/src/main/java/com/android/volley/compat/DelegateSSLSocket.java b/Clover/app/src/main/java/com/android/volley/compat/DelegateSSLSocket.java new file mode 100644 index 00000000..728934d8 --- /dev/null +++ b/Clover/app/src/main/java/com/android/volley/compat/DelegateSSLSocket.java @@ -0,0 +1,331 @@ +package com.android.volley.compat; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetAddress; +import java.net.SocketAddress; +import java.net.SocketException; +import java.nio.channels.SocketChannel; + +import javax.net.ssl.HandshakeCompletedListener; +import javax.net.ssl.SSLParameters; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +/** + * Created by robUx4 on 25/10/2014. + */ +public class DelegateSSLSocket extends SSLSocket { + + protected final SSLSocket delegate; + + DelegateSSLSocket(SSLSocket delegate) { + this.delegate = delegate; + } + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + @Override + public String[] getEnabledCipherSuites() { + return delegate.getEnabledCipherSuites(); + } + + @Override + public void setEnabledCipherSuites(String[] suites) { + delegate.setEnabledCipherSuites(suites); + } + + @Override + public String[] getSupportedProtocols() { + return delegate.getSupportedProtocols(); + } + + @Override + public String[] getEnabledProtocols() { + return delegate.getEnabledProtocols(); + } + + @Override + public void setEnabledProtocols(String[] protocols) { + delegate.setEnabledProtocols(protocols); + } + + @Override + public SSLSession getSession() { + return delegate.getSession(); + } + + @Override + public void addHandshakeCompletedListener(HandshakeCompletedListener listener) { + delegate.addHandshakeCompletedListener(listener); + } + + @Override + public void removeHandshakeCompletedListener(HandshakeCompletedListener listener) { + delegate.removeHandshakeCompletedListener(listener); + } + + @Override + public void startHandshake() throws IOException { + delegate.startHandshake(); + } + + @Override + public void setUseClientMode(boolean mode) { + delegate.setUseClientMode(mode); + } + + @Override + public boolean getUseClientMode() { + return delegate.getUseClientMode(); + } + + @Override + public void setNeedClientAuth(boolean need) { + delegate.setNeedClientAuth(need); + } + + @Override + public void setWantClientAuth(boolean want) { + delegate.setWantClientAuth(want); + } + + @Override + public boolean getNeedClientAuth() { + return delegate.getNeedClientAuth(); + } + + @Override + public boolean getWantClientAuth() { + return delegate.getWantClientAuth(); + } + + @Override + public void setEnableSessionCreation(boolean flag) { + delegate.setEnableSessionCreation(flag); + } + + @Override + public boolean getEnableSessionCreation() { + return delegate.getEnableSessionCreation(); + } + + @Override + public void bind(SocketAddress localAddr) throws IOException { + delegate.bind(localAddr); + } + + @Override + public synchronized void close() throws IOException { + delegate.close(); + } + + @Override + public void connect(SocketAddress remoteAddr) throws IOException { + delegate.connect(remoteAddr); + } + + @Override + public void connect(SocketAddress remoteAddr, int timeout) throws IOException { + delegate.connect(remoteAddr, timeout); + } + + @Override + public SocketChannel getChannel() { + return delegate.getChannel(); + } + + @Override + public InetAddress getInetAddress() { + return delegate.getInetAddress(); + } + + @Override + public InputStream getInputStream() throws IOException { + return delegate.getInputStream(); + } + + @Override + public boolean getKeepAlive() throws SocketException { + return delegate.getKeepAlive(); + } + + @Override + public InetAddress getLocalAddress() { + return delegate.getLocalAddress(); + } + + @Override + public int getLocalPort() { + return delegate.getLocalPort(); + } + + @Override + public SocketAddress getLocalSocketAddress() { + return delegate.getLocalSocketAddress(); + } + + @Override + public boolean getOOBInline() throws SocketException { + return delegate.getOOBInline(); + } + + @Override + public OutputStream getOutputStream() throws IOException { + return delegate.getOutputStream(); + } + + @Override + public int getPort() { + return delegate.getPort(); + } + + @Override + public synchronized int getReceiveBufferSize() throws SocketException { + return delegate.getReceiveBufferSize(); + } + + @Override + public SocketAddress getRemoteSocketAddress() { + return delegate.getRemoteSocketAddress(); + } + + @Override + public boolean getReuseAddress() throws SocketException { + return delegate.getReuseAddress(); + } + + @Override + public synchronized int getSendBufferSize() throws SocketException { + return delegate.getSendBufferSize(); + } + + @Override + public int getSoLinger() throws SocketException { + return delegate.getSoLinger(); + } + + @Override + public synchronized int getSoTimeout() throws SocketException { + return delegate.getSoTimeout(); + } + + @Override + public boolean getTcpNoDelay() throws SocketException { + return delegate.getTcpNoDelay(); + } + + @Override + public int getTrafficClass() throws SocketException { + return delegate.getTrafficClass(); + } + + @Override + public boolean isBound() { + return delegate.isBound(); + } + + @Override + public boolean isClosed() { + return delegate.isClosed(); + } + + @Override + public boolean isConnected() { + return delegate.isConnected(); + } + + @Override + public boolean isInputShutdown() { + return delegate.isInputShutdown(); + } + + @Override + public boolean isOutputShutdown() { + return delegate.isOutputShutdown(); + } + + @Override + public void sendUrgentData(int value) throws IOException { + delegate.sendUrgentData(value); + } + + @Override + public void setKeepAlive(boolean keepAlive) throws SocketException { + delegate.setKeepAlive(keepAlive); + } + + @Override + public void setOOBInline(boolean oobinline) throws SocketException { + delegate.setOOBInline(oobinline); + } + + @Override + public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) { + delegate.setPerformancePreferences(connectionTime, latency, bandwidth); + } + + @Override + public synchronized void setReceiveBufferSize(int size) throws SocketException { + delegate.setReceiveBufferSize(size); + } + + @Override + public void setReuseAddress(boolean reuse) throws SocketException { + delegate.setReuseAddress(reuse); + } + + @Override + public synchronized void setSendBufferSize(int size) throws SocketException { + delegate.setSendBufferSize(size); + } + + @Override + public void setSoLinger(boolean on, int timeout) throws SocketException { + delegate.setSoLinger(on, timeout); + } + + @Override + public synchronized void setSoTimeout(int timeout) throws SocketException { + delegate.setSoTimeout(timeout); + } + + @Override + public void setSSLParameters(SSLParameters p) { + delegate.setSSLParameters(p); + } + + @Override + public void setTcpNoDelay(boolean on) throws SocketException { + delegate.setTcpNoDelay(on); + } + + @Override + public void setTrafficClass(int value) throws SocketException { + delegate.setTrafficClass(value); + } + + @Override + public void shutdownInput() throws IOException { + delegate.shutdownInput(); + } + + @Override + public void shutdownOutput() throws IOException { + delegate.shutdownOutput(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public boolean equals(Object o) { + return delegate.equals(o); + } +} diff --git a/Clover/app/src/main/java/com/android/volley/compat/NoSSLv3Compat.java b/Clover/app/src/main/java/com/android/volley/compat/NoSSLv3Compat.java new file mode 100644 index 00000000..f8d82fe0 --- /dev/null +++ b/Clover/app/src/main/java/com/android/volley/compat/NoSSLv3Compat.java @@ -0,0 +1,113 @@ +package com.android.volley.compat; + +import com.android.volley.VolleyLog; + +import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSocketFactory; + +public class NoSSLv3Compat { + /** + * An {@link javax.net.ssl.SSLSocket} that doesn't allow {@code SSLv3} only connections + *

fixes https://github.com/koush/ion/issues/386

+ *

Android bug report: https://code.google.com/p/android/issues/detail?id=78187

+ */ + private static class NoSSLv3SSLSocket extends DelegateSSLSocket { + + private NoSSLv3SSLSocket(SSLSocket delegate) { + super(delegate); + + /*String canonicalName = delegate.getClass().getCanonicalName(); + if (!canonicalName.equals("org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl")) { + // try replicate the code from HttpConnection.setupSecureSocket() + try { + Method msetUseSessionTickets = delegate.getClass().getMethod("setUseSessionTickets", boolean.class); + if (null != msetUseSessionTickets) { + msetUseSessionTickets.invoke(delegate, true); + } + } catch (NoSuchMethodException ignored) { + } catch (InvocationTargetException ignored) { + } catch (IllegalAccessException ignored) { + } + }*/ + } + + @Override + public void setEnabledProtocols(String[] protocols) { + if (protocols != null && protocols.length == 1 && "SSLv3".equals(protocols[0])) { + // no way jose + // see issue https://code.google.com/p/android/issues/detail?id=78187 + List enabledProtocols = new ArrayList<>(Arrays.asList(delegate.getEnabledProtocols())); + if (enabledProtocols.size() > 1) { + enabledProtocols.remove("SSLv3"); + VolleyLog.d("Removed SSLv3 from enabled protocols"); + } else { + VolleyLog.d("SSL stuck with protocol available for " + String.valueOf(enabledProtocols)); + } + protocols = enabledProtocols.toArray(new String[enabledProtocols.size()]); + } + + super.setEnabledProtocols(protocols); + } + } + + /** + * {@link javax.net.ssl.SSLSocketFactory} that doesn't allow {@code SSLv3} only connections + */ + public static class NoSSLv3Factory extends SSLSocketFactory { + private final SSLSocketFactory delegate; + + public NoSSLv3Factory() { + this.delegate = HttpsURLConnection.getDefaultSSLSocketFactory(); + } + + @Override + public String[] getDefaultCipherSuites() { + return delegate.getDefaultCipherSuites(); + } + + @Override + public String[] getSupportedCipherSuites() { + return delegate.getSupportedCipherSuites(); + } + + private static Socket makeSocketSafe(Socket socket) { + if (socket instanceof SSLSocket) { + socket = new NoSSLv3SSLSocket((SSLSocket) socket); + } + return socket; + } + + @Override + public Socket createSocket(Socket s, String host, int port, boolean autoClose) throws IOException { + return makeSocketSafe(delegate.createSocket(s, host, port, autoClose)); + } + + @Override + public Socket createSocket(String host, int port) throws IOException { + return makeSocketSafe(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(String host, int port, InetAddress localHost, int localPort) throws IOException { + return makeSocketSafe(delegate.createSocket(host, port, localHost, localPort)); + } + + @Override + public Socket createSocket(InetAddress host, int port) throws IOException { + return makeSocketSafe(delegate.createSocket(host, port)); + } + + @Override + public Socket createSocket(InetAddress address, int port, InetAddress localAddress, int localPort) throws IOException { + return makeSocketSafe(delegate.createSocket(address, port, localAddress, localPort)); + } + } +} diff --git a/Clover/app/src/main/java/com/android/volley/toolbox/BasicNetwork.java b/Clover/app/src/main/java/com/android/volley/toolbox/BasicNetwork.java index c82fc34a..c76ef6ab 100644 --- a/Clover/app/src/main/java/com/android/volley/toolbox/BasicNetwork.java +++ b/Clover/app/src/main/java/com/android/volley/toolbox/BasicNetwork.java @@ -20,6 +20,7 @@ import android.os.SystemClock; import com.android.volley.AuthFailureError; import com.android.volley.Cache; +import com.android.volley.Cache.Entry; import com.android.volley.Network; import com.android.volley.NetworkError; import com.android.volley.NetworkResponse; @@ -43,9 +44,11 @@ import java.io.IOException; import java.io.InputStream; import java.net.MalformedURLException; import java.net.SocketTimeoutException; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.TreeMap; /** * A network performing Volley requests over an {@link HttpStack}. @@ -85,7 +88,7 @@ public class BasicNetwork implements Network { while (true) { HttpResponse httpResponse = null; byte[] responseContents = null; - Map responseHeaders = new HashMap(); + Map responseHeaders = Collections.emptyMap(); try { // Gather headers. Map headers = new HashMap(); @@ -97,9 +100,20 @@ public class BasicNetwork implements Network { responseHeaders = convertHeaders(httpResponse.getAllHeaders()); // Handle cache validation. if (statusCode == HttpStatus.SC_NOT_MODIFIED) { - return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, - request.getCacheEntry() == null ? null : request.getCacheEntry().data, - responseHeaders, true); + + Entry entry = request.getCacheEntry(); + if (entry == null) { + return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null, + responseHeaders, true); + } + + // A HTTP 304 response does not have all header fields. We + // have to use the header fields from the cache entry plus + // the new ones from the response. + // http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5 + entry.responseHeaders.putAll(responseHeaders); + return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data, + entry.responseHeaders, true); } // Some responses such as 204s do not have content. We must check. @@ -239,8 +253,8 @@ public class BasicNetwork implements Network { /** * Converts Headers[] to Map. */ - private static Map convertHeaders(Header[] headers) { - Map result = new HashMap(); + protected static Map convertHeaders(Header[] headers) { + Map result = new TreeMap(String.CASE_INSENSITIVE_ORDER); for (int i = 0; i < headers.length; i++) { result.put(headers[i].getName(), headers[i].getValue()); } diff --git a/Clover/app/src/main/java/com/android/volley/toolbox/ImageLoader.java b/Clover/app/src/main/java/com/android/volley/toolbox/ImageLoader.java index fe0f32ad..5348dc69 100644 --- a/Clover/app/src/main/java/com/android/volley/toolbox/ImageLoader.java +++ b/Clover/app/src/main/java/com/android/volley/toolbox/ImageLoader.java @@ -26,7 +26,6 @@ import com.android.volley.RequestQueue; import com.android.volley.Response.ErrorListener; import com.android.volley.Response.Listener; import com.android.volley.VolleyError; -import com.android.volley.toolbox.ImageRequest; import java.util.HashMap; import java.util.LinkedList; @@ -216,19 +215,7 @@ public class ImageLoader { // The request is not already in flight. Send the new request to the network and // track it. - Request newRequest = - new ImageRequest(requestUrl, new Listener() { - @Override - public void onResponse(Bitmap response) { - onGetImageSuccess(cacheKey, response); - } - }, maxWidth, maxHeight, - Config.RGB_565, new ErrorListener() { - @Override - public void onErrorResponse(VolleyError error) { - onGetImageError(cacheKey, error); - } - }); + Request newRequest = makeImageRequest(requestUrl, maxWidth, maxHeight, cacheKey); mRequestQueue.add(newRequest); mInFlightRequests.put(cacheKey, @@ -236,6 +223,21 @@ public class ImageLoader { return imageContainer; } + protected Request makeImageRequest(String requestUrl, int maxWidth, int maxHeight, final String cacheKey) { + return new ImageRequest(requestUrl, new Listener() { + @Override + public void onResponse(Bitmap response) { + onGetImageSuccess(cacheKey, response); + } + }, maxWidth, maxHeight, + Config.RGB_565, new ErrorListener() { + @Override + public void onErrorResponse(VolleyError error) { + onGetImageError(cacheKey, error); + } + }); + } + /** * Sets the amount of time to wait after the first response arrives before delivering all * responses. Batching can be disabled entirely by passing in 0. @@ -250,7 +252,7 @@ public class ImageLoader { * @param cacheKey The cache key that is associated with the image request. * @param response The bitmap that was returned from the network. */ - private void onGetImageSuccess(String cacheKey, Bitmap response) { + protected void onGetImageSuccess(String cacheKey, Bitmap response) { // cache the image that was fetched. mCache.putBitmap(cacheKey, response); @@ -270,7 +272,7 @@ public class ImageLoader { * Handler for when an image failed to load. * @param cacheKey The cache key that is associated with the image request. */ - private void onGetImageError(String cacheKey, VolleyError error) { + protected void onGetImageError(String cacheKey, VolleyError error) { // Notify the requesters that something failed via a null result. // Remove this request from the list of in-flight requests. BatchedImageRequest request = mInFlightRequests.remove(cacheKey); diff --git a/Clover/app/src/main/java/com/android/volley/toolbox/Volley.java b/Clover/app/src/main/java/com/android/volley/toolbox/Volley.java index a031413f..37c18a25 100644 --- a/Clover/app/src/main/java/com/android/volley/toolbox/Volley.java +++ b/Clover/app/src/main/java/com/android/volley/toolbox/Volley.java @@ -24,6 +24,7 @@ import android.os.Build; import com.android.volley.Network; import com.android.volley.RequestQueue; +import com.android.volley.compat.NoSSLv3Compat; import java.io.File; @@ -59,7 +60,12 @@ public class Volley { if (stack == null) { if (Build.VERSION.SDK_INT >= 9) { - stack = new HurlStack(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) { + // Use a socket factory that removes sslv3 + stack = new HurlStack(null, new NoSSLv3Compat.NoSSLv3Factory()); + } else { + stack = new HurlStack(); + } } else { // Prior to Gingerbread, HttpUrlConnection was unreliable. // See: http://android-developers.blogspot.com/2011/09/androids-http-clients.html