The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2007 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
Dianne Hackborn | 2269d157 | 2010-02-24 19:54:22 -0800 | [diff] [blame] | 17 | package android.net.http; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 18 | |
Jesse Wilson | 7cfa90f | 2010-04-08 14:20:57 -0700 | [diff] [blame] | 19 | import com.android.internal.http.HttpDateTime; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 20 | import org.apache.http.Header; |
| 21 | import org.apache.http.HttpEntity; |
| 22 | import org.apache.http.HttpEntityEnclosingRequest; |
| 23 | import org.apache.http.HttpException; |
| 24 | import org.apache.http.HttpHost; |
| 25 | import org.apache.http.HttpRequest; |
| 26 | import org.apache.http.HttpRequestInterceptor; |
| 27 | import org.apache.http.HttpResponse; |
Brian Carlstrom | 992f238 | 2012-09-26 14:33:47 -0700 | [diff] [blame] | 28 | import org.apache.http.entity.AbstractHttpEntity; |
| 29 | import org.apache.http.entity.ByteArrayEntity; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 30 | import org.apache.http.client.HttpClient; |
| 31 | import org.apache.http.client.ResponseHandler; |
Brian Carlstrom | 992f238 | 2012-09-26 14:33:47 -0700 | [diff] [blame] | 32 | import org.apache.http.client.ClientProtocolException; |
| 33 | import org.apache.http.client.protocol.ClientContext; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 34 | import org.apache.http.client.methods.HttpUriRequest; |
| 35 | import org.apache.http.client.params.HttpClientParams; |
| 36 | import org.apache.http.conn.ClientConnectionManager; |
| 37 | import org.apache.http.conn.scheme.PlainSocketFactory; |
| 38 | import org.apache.http.conn.scheme.Scheme; |
| 39 | import org.apache.http.conn.scheme.SchemeRegistry; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 40 | import org.apache.http.impl.client.DefaultHttpClient; |
| 41 | import org.apache.http.impl.client.RequestWrapper; |
| 42 | import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager; |
| 43 | import org.apache.http.params.BasicHttpParams; |
| 44 | import org.apache.http.params.HttpConnectionParams; |
| 45 | import org.apache.http.params.HttpParams; |
| 46 | import org.apache.http.params.HttpProtocolParams; |
| 47 | import org.apache.http.protocol.BasicHttpProcessor; |
| 48 | import org.apache.http.protocol.HttpContext; |
Brian Carlstrom | 992f238 | 2012-09-26 14:33:47 -0700 | [diff] [blame] | 49 | import org.apache.http.protocol.BasicHttpContext; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 50 | |
| 51 | import java.io.IOException; |
| 52 | import java.io.InputStream; |
Brian Carlstrom | 992f238 | 2012-09-26 14:33:47 -0700 | [diff] [blame] | 53 | import java.io.ByteArrayOutputStream; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 54 | import java.io.OutputStream; |
| 55 | import java.util.zip.GZIPInputStream; |
| 56 | import java.util.zip.GZIPOutputStream; |
Brian Carlstrom | 992f238 | 2012-09-26 14:33:47 -0700 | [diff] [blame] | 57 | import java.net.URI; |
| 58 | |
| 59 | import android.content.Context; |
| 60 | import android.content.ContentResolver; |
| 61 | import android.net.SSLCertificateSocketFactory; |
| 62 | import android.net.SSLSessionCache; |
| 63 | import android.os.Looper; |
| 64 | import android.util.Base64; |
| 65 | import android.util.Log; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 66 | |
| 67 | /** |
Elliott Hughes | 7b91b31 | 2011-07-12 16:02:50 -0700 | [diff] [blame] | 68 | * Implementation of the Apache {@link DefaultHttpClient} that is configured with |
Elliott Hughes | d2dcd7a | 2012-11-29 08:32:13 -0800 | [diff] [blame] | 69 | * reasonable default settings and registered schemes for Android. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 70 | * Don't create this directly, use the {@link #newInstance} factory method. |
| 71 | * |
| 72 | * <p>This client processes cookies but does not retain them by default. |
| 73 | * To retain cookies, simply add a cookie store to the HttpContext:</p> |
| 74 | * |
| 75 | * <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre> |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 76 | */ |
| 77 | public final class AndroidHttpClient implements HttpClient { |
Dan Egnor | 60586f2 | 2010-02-08 21:56:38 -0800 | [diff] [blame] | 78 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 79 | // Gzip of data shorter than this probably won't be worthwhile |
| 80 | public static long DEFAULT_SYNC_MIN_GZIP_BYTES = 256; |
| 81 | |
Henrik Baard | d39fd5a | 2010-11-18 14:08:36 +0100 | [diff] [blame] | 82 | // Default connection and socket timeout of 60 seconds. Tweak to taste. |
| 83 | private static final int SOCKET_OPERATION_TIMEOUT = 60 * 1000; |
| 84 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 85 | private static final String TAG = "AndroidHttpClient"; |
| 86 | |
Alon Albert | d81689a | 2010-06-03 15:43:14 -0700 | [diff] [blame] | 87 | private static String[] textContentTypes = new String[] { |
| 88 | "text/", |
| 89 | "application/xml", |
| 90 | "application/json" |
| 91 | }; |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 92 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 93 | /** Interceptor throws an exception if the executing thread is blocked */ |
| 94 | private static final HttpRequestInterceptor sThreadCheckInterceptor = |
| 95 | new HttpRequestInterceptor() { |
| 96 | public void process(HttpRequest request, HttpContext context) { |
Paul Westbrook | 7762d93 | 2009-12-11 14:13:48 -0800 | [diff] [blame] | 97 | // Prevent the HttpRequest from being sent on the main thread |
| 98 | if (Looper.myLooper() != null && Looper.myLooper() == Looper.getMainLooper() ) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 99 | throw new RuntimeException("This thread forbids HTTP requests"); |
| 100 | } |
| 101 | } |
| 102 | }; |
| 103 | |
| 104 | /** |
| 105 | * Create a new HttpClient with reasonable defaults (which you can update). |
| 106 | * |
Dan Egnor | 60586f2 | 2010-02-08 21:56:38 -0800 | [diff] [blame] | 107 | * @param userAgent to report in your HTTP requests |
| 108 | * @param context to use for caching SSL sessions (may be null for no caching) |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 109 | * @return AndroidHttpClient for you to use for all your requests. |
| 110 | */ |
Dan Egnor | 60586f2 | 2010-02-08 21:56:38 -0800 | [diff] [blame] | 111 | public static AndroidHttpClient newInstance(String userAgent, Context context) { |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 112 | HttpParams params = new BasicHttpParams(); |
| 113 | |
| 114 | // Turn off stale checking. Our connections break all the time anyway, |
| 115 | // and it's not worth it to pay the penalty of checking every time. |
| 116 | HttpConnectionParams.setStaleCheckingEnabled(params, false); |
| 117 | |
Henrik Baard | d39fd5a | 2010-11-18 14:08:36 +0100 | [diff] [blame] | 118 | HttpConnectionParams.setConnectionTimeout(params, SOCKET_OPERATION_TIMEOUT); |
| 119 | HttpConnectionParams.setSoTimeout(params, SOCKET_OPERATION_TIMEOUT); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 120 | HttpConnectionParams.setSocketBufferSize(params, 8192); |
| 121 | |
| 122 | // Don't handle redirects -- return them to the caller. Our code |
| 123 | // often wants to re-POST after a redirect, which we must do ourselves. |
| 124 | HttpClientParams.setRedirecting(params, false); |
| 125 | |
Dan Egnor | 60586f2 | 2010-02-08 21:56:38 -0800 | [diff] [blame] | 126 | // Use a session cache for SSL sockets |
| 127 | SSLSessionCache sessionCache = context == null ? null : new SSLSessionCache(context); |
| 128 | |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 129 | // Set the specified user agent and register standard protocols. |
| 130 | HttpProtocolParams.setUserAgent(params, userAgent); |
| 131 | SchemeRegistry schemeRegistry = new SchemeRegistry(); |
| 132 | schemeRegistry.register(new Scheme("http", |
| 133 | PlainSocketFactory.getSocketFactory(), 80)); |
| 134 | schemeRegistry.register(new Scheme("https", |
Henrik Baard | d39fd5a | 2010-11-18 14:08:36 +0100 | [diff] [blame] | 135 | SSLCertificateSocketFactory.getHttpSocketFactory( |
Brian Carlstrom | 992f238 | 2012-09-26 14:33:47 -0700 | [diff] [blame] | 136 | SOCKET_OPERATION_TIMEOUT, sessionCache), 443)); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 137 | |
| 138 | ClientConnectionManager manager = |
| 139 | new ThreadSafeClientConnManager(params, schemeRegistry); |
| 140 | |
| 141 | // We use a factory method to modify superclass initialization |
| 142 | // parameters without the funny call-a-static-method dance. |
| 143 | return new AndroidHttpClient(manager, params); |
| 144 | } |
| 145 | |
| 146 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 147 | * Create a new HttpClient with reasonable defaults (which you can update). |
| 148 | * @param userAgent to report in your HTTP requests. |
| 149 | * @return AndroidHttpClient for you to use for all your requests. |
| 150 | */ |
| 151 | public static AndroidHttpClient newInstance(String userAgent) { |
| 152 | return newInstance(userAgent, null /* session cache */); |
| 153 | } |
| 154 | |
| 155 | private final HttpClient delegate; |
| 156 | |
| 157 | private RuntimeException mLeakedException = new IllegalStateException( |
| 158 | "AndroidHttpClient created and never closed"); |
| 159 | |
| 160 | private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) { |
| 161 | this.delegate = new DefaultHttpClient(ccm, params) { |
| 162 | @Override |
| 163 | protected BasicHttpProcessor createHttpProcessor() { |
| 164 | // Add interceptor to prevent making requests from main thread. |
| 165 | BasicHttpProcessor processor = super.createHttpProcessor(); |
| 166 | processor.addRequestInterceptor(sThreadCheckInterceptor); |
| 167 | processor.addRequestInterceptor(new CurlLogger()); |
| 168 | |
| 169 | return processor; |
| 170 | } |
| 171 | |
| 172 | @Override |
| 173 | protected HttpContext createHttpContext() { |
| 174 | // Same as DefaultHttpClient.createHttpContext() minus the |
| 175 | // cookie store. |
| 176 | HttpContext context = new BasicHttpContext(); |
| 177 | context.setAttribute( |
| 178 | ClientContext.AUTHSCHEME_REGISTRY, |
| 179 | getAuthSchemes()); |
| 180 | context.setAttribute( |
| 181 | ClientContext.COOKIESPEC_REGISTRY, |
| 182 | getCookieSpecs()); |
| 183 | context.setAttribute( |
| 184 | ClientContext.CREDS_PROVIDER, |
| 185 | getCredentialsProvider()); |
| 186 | return context; |
| 187 | } |
| 188 | }; |
| 189 | } |
| 190 | |
| 191 | @Override |
| 192 | protected void finalize() throws Throwable { |
| 193 | super.finalize(); |
| 194 | if (mLeakedException != null) { |
| 195 | Log.e(TAG, "Leak found", mLeakedException); |
| 196 | mLeakedException = null; |
| 197 | } |
| 198 | } |
| 199 | |
| 200 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 201 | * Modifies a request to indicate to the server that we would like a |
| 202 | * gzipped response. (Uses the "Accept-Encoding" HTTP header.) |
| 203 | * @param request the request to modify |
| 204 | * @see #getUngzippedContent |
| 205 | */ |
| 206 | public static void modifyRequestToAcceptGzipResponse(HttpRequest request) { |
| 207 | request.addHeader("Accept-Encoding", "gzip"); |
| 208 | } |
| 209 | |
| 210 | /** |
| 211 | * Gets the input stream from a response entity. If the entity is gzipped |
| 212 | * then this will get a stream over the uncompressed data. |
| 213 | * |
| 214 | * @param entity the entity whose content should be read |
| 215 | * @return the input stream to read from |
| 216 | * @throws IOException |
| 217 | */ |
| 218 | public static InputStream getUngzippedContent(HttpEntity entity) |
| 219 | throws IOException { |
| 220 | InputStream responseStream = entity.getContent(); |
| 221 | if (responseStream == null) return responseStream; |
| 222 | Header header = entity.getContentEncoding(); |
| 223 | if (header == null) return responseStream; |
| 224 | String contentEncoding = header.getValue(); |
| 225 | if (contentEncoding == null) return responseStream; |
| 226 | if (contentEncoding.contains("gzip")) responseStream |
| 227 | = new GZIPInputStream(responseStream); |
| 228 | return responseStream; |
| 229 | } |
| 230 | |
| 231 | /** |
| 232 | * Release resources associated with this client. You must call this, |
| 233 | * or significant resources (sockets and memory) may be leaked. |
| 234 | */ |
| 235 | public void close() { |
| 236 | if (mLeakedException != null) { |
| 237 | getConnectionManager().shutdown(); |
| 238 | mLeakedException = null; |
| 239 | } |
| 240 | } |
| 241 | |
| 242 | public HttpParams getParams() { |
| 243 | return delegate.getParams(); |
| 244 | } |
| 245 | |
| 246 | public ClientConnectionManager getConnectionManager() { |
| 247 | return delegate.getConnectionManager(); |
| 248 | } |
| 249 | |
| 250 | public HttpResponse execute(HttpUriRequest request) throws IOException { |
| 251 | return delegate.execute(request); |
| 252 | } |
| 253 | |
| 254 | public HttpResponse execute(HttpUriRequest request, HttpContext context) |
| 255 | throws IOException { |
| 256 | return delegate.execute(request, context); |
| 257 | } |
| 258 | |
| 259 | public HttpResponse execute(HttpHost target, HttpRequest request) |
| 260 | throws IOException { |
| 261 | return delegate.execute(target, request); |
| 262 | } |
| 263 | |
| 264 | public HttpResponse execute(HttpHost target, HttpRequest request, |
| 265 | HttpContext context) throws IOException { |
| 266 | return delegate.execute(target, request, context); |
| 267 | } |
| 268 | |
| 269 | public <T> T execute(HttpUriRequest request, |
| 270 | ResponseHandler<? extends T> responseHandler) |
| 271 | throws IOException, ClientProtocolException { |
| 272 | return delegate.execute(request, responseHandler); |
| 273 | } |
| 274 | |
| 275 | public <T> T execute(HttpUriRequest request, |
| 276 | ResponseHandler<? extends T> responseHandler, HttpContext context) |
| 277 | throws IOException, ClientProtocolException { |
| 278 | return delegate.execute(request, responseHandler, context); |
| 279 | } |
| 280 | |
| 281 | public <T> T execute(HttpHost target, HttpRequest request, |
| 282 | ResponseHandler<? extends T> responseHandler) throws IOException, |
| 283 | ClientProtocolException { |
| 284 | return delegate.execute(target, request, responseHandler); |
| 285 | } |
| 286 | |
| 287 | public <T> T execute(HttpHost target, HttpRequest request, |
| 288 | ResponseHandler<? extends T> responseHandler, HttpContext context) |
| 289 | throws IOException, ClientProtocolException { |
| 290 | return delegate.execute(target, request, responseHandler, context); |
| 291 | } |
| 292 | |
| 293 | /** |
| 294 | * Compress data to send to server. |
| 295 | * Creates a Http Entity holding the gzipped data. |
| 296 | * The data will not be compressed if it is too short. |
| 297 | * @param data The bytes to compress |
| 298 | * @return Entity holding the data |
| 299 | */ |
| 300 | public static AbstractHttpEntity getCompressedEntity(byte data[], ContentResolver resolver) |
| 301 | throws IOException { |
| 302 | AbstractHttpEntity entity; |
| 303 | if (data.length < getMinGzipSize(resolver)) { |
| 304 | entity = new ByteArrayEntity(data); |
| 305 | } else { |
| 306 | ByteArrayOutputStream arr = new ByteArrayOutputStream(); |
| 307 | OutputStream zipper = new GZIPOutputStream(arr); |
| 308 | zipper.write(data); |
| 309 | zipper.close(); |
| 310 | entity = new ByteArrayEntity(arr.toByteArray()); |
| 311 | entity.setContentEncoding("gzip"); |
| 312 | } |
| 313 | return entity; |
| 314 | } |
| 315 | |
| 316 | /** |
| 317 | * Retrieves the minimum size for compressing data. |
| 318 | * Shorter data will not be compressed. |
| 319 | */ |
| 320 | public static long getMinGzipSize(ContentResolver resolver) { |
Dan Egnor | 60586f2 | 2010-02-08 21:56:38 -0800 | [diff] [blame] | 321 | return DEFAULT_SYNC_MIN_GZIP_BYTES; // For now, this is just a constant. |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 322 | } |
| 323 | |
| 324 | /* cURL logging support. */ |
| 325 | |
| 326 | /** |
| 327 | * Logging tag and level. |
| 328 | */ |
| 329 | private static class LoggingConfiguration { |
| 330 | |
| 331 | private final String tag; |
| 332 | private final int level; |
| 333 | |
| 334 | private LoggingConfiguration(String tag, int level) { |
| 335 | this.tag = tag; |
| 336 | this.level = level; |
| 337 | } |
| 338 | |
| 339 | /** |
| 340 | * Returns true if logging is turned on for this configuration. |
| 341 | */ |
| 342 | private boolean isLoggable() { |
| 343 | return Log.isLoggable(tag, level); |
| 344 | } |
| 345 | |
| 346 | /** |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 347 | * Prints a message using this configuration. |
| 348 | */ |
| 349 | private void println(String message) { |
| 350 | Log.println(level, tag, message); |
| 351 | } |
| 352 | } |
| 353 | |
| 354 | /** cURL logging configuration. */ |
| 355 | private volatile LoggingConfiguration curlConfiguration; |
| 356 | |
| 357 | /** |
| 358 | * Enables cURL request logging for this client. |
| 359 | * |
| 360 | * @param name to log messages with |
| 361 | * @param level at which to log messages (see {@link android.util.Log}) |
| 362 | */ |
| 363 | public void enableCurlLogging(String name, int level) { |
| 364 | if (name == null) { |
| 365 | throw new NullPointerException("name"); |
| 366 | } |
| 367 | if (level < Log.VERBOSE || level > Log.ASSERT) { |
| 368 | throw new IllegalArgumentException("Level is out of range [" |
Alon Albert | d81689a | 2010-06-03 15:43:14 -0700 | [diff] [blame] | 369 | + Log.VERBOSE + ".." + Log.ASSERT + "]"); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 370 | } |
| 371 | |
| 372 | curlConfiguration = new LoggingConfiguration(name, level); |
| 373 | } |
| 374 | |
| 375 | /** |
| 376 | * Disables cURL logging for this client. |
| 377 | */ |
| 378 | public void disableCurlLogging() { |
| 379 | curlConfiguration = null; |
| 380 | } |
| 381 | |
| 382 | /** |
| 383 | * Logs cURL commands equivalent to requests. |
| 384 | */ |
| 385 | private class CurlLogger implements HttpRequestInterceptor { |
| 386 | public void process(HttpRequest request, HttpContext context) |
| 387 | throws HttpException, IOException { |
| 388 | LoggingConfiguration configuration = curlConfiguration; |
| 389 | if (configuration != null |
| 390 | && configuration.isLoggable() |
| 391 | && request instanceof HttpUriRequest) { |
Dan Egnor | 60586f2 | 2010-02-08 21:56:38 -0800 | [diff] [blame] | 392 | // Never print auth token -- we used to check ro.secure=0 to |
| 393 | // enable that, but can't do that in unbundled code. |
| 394 | configuration.println(toCurl((HttpUriRequest) request, false)); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 395 | } |
| 396 | } |
| 397 | } |
| 398 | |
| 399 | /** |
| 400 | * Generates a cURL command equivalent to the given request. |
| 401 | */ |
| 402 | private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException { |
| 403 | StringBuilder builder = new StringBuilder(); |
| 404 | |
| 405 | builder.append("curl "); |
| 406 | |
| 407 | for (Header header: request.getAllHeaders()) { |
| 408 | if (!logAuthToken |
| 409 | && (header.getName().equals("Authorization") || |
| 410 | header.getName().equals("Cookie"))) { |
| 411 | continue; |
| 412 | } |
| 413 | builder.append("--header \""); |
| 414 | builder.append(header.toString().trim()); |
| 415 | builder.append("\" "); |
| 416 | } |
| 417 | |
| 418 | URI uri = request.getURI(); |
| 419 | |
| 420 | // If this is a wrapped request, use the URI from the original |
| 421 | // request instead. getURI() on the wrapper seems to return a |
| 422 | // relative URI. We want an absolute URI. |
| 423 | if (request instanceof RequestWrapper) { |
| 424 | HttpRequest original = ((RequestWrapper) request).getOriginal(); |
| 425 | if (original instanceof HttpUriRequest) { |
| 426 | uri = ((HttpUriRequest) original).getURI(); |
| 427 | } |
| 428 | } |
| 429 | |
| 430 | builder.append("\""); |
| 431 | builder.append(uri); |
| 432 | builder.append("\""); |
| 433 | |
| 434 | if (request instanceof HttpEntityEnclosingRequest) { |
| 435 | HttpEntityEnclosingRequest entityRequest = |
| 436 | (HttpEntityEnclosingRequest) request; |
| 437 | HttpEntity entity = entityRequest.getEntity(); |
| 438 | if (entity != null && entity.isRepeatable()) { |
| 439 | if (entity.getContentLength() < 1024) { |
| 440 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); |
| 441 | entity.writeTo(stream); |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 442 | |
Alon Albert | d81689a | 2010-06-03 15:43:14 -0700 | [diff] [blame] | 443 | if (isBinaryContent(request)) { |
| 444 | String base64 = Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP); |
| 445 | builder.insert(0, "echo '" + base64 + "' | base64 -d > /tmp/$$.bin; "); |
| 446 | builder.append(" --data-binary @/tmp/$$.bin"); |
| 447 | } else { |
| 448 | String entityString = stream.toString(); |
| 449 | builder.append(" --data-ascii \"") |
| 450 | .append(entityString) |
| 451 | .append("\""); |
| 452 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 453 | } else { |
| 454 | builder.append(" [TOO MUCH DATA TO INCLUDE]"); |
| 455 | } |
| 456 | } |
| 457 | } |
| 458 | |
| 459 | return builder.toString(); |
| 460 | } |
Jesse Wilson | 7cfa90f | 2010-04-08 14:20:57 -0700 | [diff] [blame] | 461 | |
Alon Albert | d81689a | 2010-06-03 15:43:14 -0700 | [diff] [blame] | 462 | private static boolean isBinaryContent(HttpUriRequest request) { |
| 463 | Header[] headers; |
| 464 | headers = request.getHeaders(Headers.CONTENT_ENCODING); |
| 465 | if (headers != null) { |
| 466 | for (Header header : headers) { |
| 467 | if ("gzip".equalsIgnoreCase(header.getValue())) { |
| 468 | return true; |
| 469 | } |
| 470 | } |
| 471 | } |
| 472 | |
| 473 | headers = request.getHeaders(Headers.CONTENT_TYPE); |
| 474 | if (headers != null) { |
| 475 | for (Header header : headers) { |
| 476 | for (String contentType : textContentTypes) { |
| 477 | if (header.getValue().startsWith(contentType)) { |
| 478 | return false; |
| 479 | } |
| 480 | } |
| 481 | } |
| 482 | } |
| 483 | return true; |
| 484 | } |
| 485 | |
Jesse Wilson | 7cfa90f | 2010-04-08 14:20:57 -0700 | [diff] [blame] | 486 | /** |
| 487 | * Returns the date of the given HTTP date string. This method can identify |
| 488 | * and parse the date formats emitted by common HTTP servers, such as |
| 489 | * <a href="http://www.ietf.org/rfc/rfc0822.txt">RFC 822</a>, |
| 490 | * <a href="http://www.ietf.org/rfc/rfc0850.txt">RFC 850</a>, |
| 491 | * <a href="http://www.ietf.org/rfc/rfc1036.txt">RFC 1036</a>, |
| 492 | * <a href="http://www.ietf.org/rfc/rfc1123.txt">RFC 1123</a> and |
| 493 | * <a href="http://www.opengroup.org/onlinepubs/007908799/xsh/asctime.html">ANSI |
| 494 | * C's asctime()</a>. |
| 495 | * |
| 496 | * @return the number of milliseconds since Jan. 1, 1970, midnight GMT. |
| 497 | * @throws IllegalArgumentException if {@code dateString} is not a date or |
| 498 | * of an unsupported format. |
| 499 | */ |
| 500 | public static long parseDate(String dateString) { |
| 501 | return HttpDateTime.parse(dateString); |
| 502 | } |
The Android Open Source Project | 9066cfe | 2009-03-03 19:31:44 -0800 | [diff] [blame] | 503 | } |