1// Copyright 2013 Google Inc. All Rights Reserved.
2
3package com.android.exchange.utility;
4
5import android.os.Build;
6import android.util.Base64;
7import android.util.Log;
8
9import com.android.exchange.Eas;
10
11import org.apache.http.Header;
12import org.apache.http.HttpEntity;
13import org.apache.http.HttpEntityEnclosingRequest;
14import org.apache.http.HttpRequest;
15import org.apache.http.HttpRequestInterceptor;
16import org.apache.http.client.methods.HttpUriRequest;
17import org.apache.http.impl.client.RequestWrapper;
18import org.apache.http.protocol.HttpContext;
19
20import java.io.ByteArrayOutputStream;
21import java.io.IOException;
22import java.net.URI;
23
24/**
25 * Logs cURL commands equivalent to requests.
26 * Curl Logging is copied over from AndroidHttpClient. Just switching to AndroidHttpClient is
27 * not trivial so it's easier to borrow the curl logging code this way.
28 */
29public class CurlLogger implements HttpRequestInterceptor {
30    private static final String TAG = Eas.LOG_TAG;
31
32    @Override
33    public void process(HttpRequest request, HttpContext context) throws IOException {
34        if (request instanceof HttpUriRequest) {
35            if ((Build.TYPE.equals("userdebug") || Build.TYPE.equals("eng"))
36                    &&  Log.isLoggable(TAG, Log.VERBOSE)) {
37                // Allow us to log auth token on dev devices - this is not a big security risk
38                // because dev devices have a readable account.db file where all the auth tokens
39                // are stored.
40                Log.d(TAG, toCurl((HttpUriRequest) request, true));
41            } else  if (Log.isLoggable(TAG, Log.DEBUG)) {
42                Log.d(TAG, toCurl((HttpUriRequest) request, false));
43            }
44        }
45    }
46
47    /**
48     * Generates a cURL command equivalent to the given request.
49     */
50    private static String toCurl(HttpUriRequest request, boolean logAuthToken) throws IOException {
51        StringBuilder builder = new StringBuilder();
52
53        builder.append("curl ");
54
55        for (Header header: request.getAllHeaders()) {
56            builder.append("--header \"");
57            if (!logAuthToken
58                    && (header.getName().equals("Authorization") ||
59                    header.getName().equals("Cookie"))) {
60
61                builder.append(header.getName()).append(": ").append("${token}");
62            } else {
63                builder.append(header.toString().trim());
64            }
65            builder.append("\" ");
66        }
67
68        URI uri = request.getURI();
69
70        // If this is a wrapped request, use the URI from the original
71        // request instead. getURI() on the wrapper seems to return a
72        // relative URI. We want an absolute URI.
73        if (request instanceof RequestWrapper) {
74            HttpRequest original = ((RequestWrapper) request).getOriginal();
75            if (original instanceof HttpUriRequest) {
76                uri = ((HttpUriRequest) original).getURI();
77            }
78        }
79
80        builder.append("\"");
81        builder.append(uri);
82        builder.append("\"");
83
84        if (request instanceof HttpEntityEnclosingRequest) {
85            HttpEntityEnclosingRequest entityRequest =
86                    (HttpEntityEnclosingRequest) request;
87            HttpEntity entity = entityRequest.getEntity();
88            if (entity != null && entity.isRepeatable()) {
89                if (entity.getContentLength() < 1024) {
90                    ByteArrayOutputStream stream = new ByteArrayOutputStream();
91                    entity.writeTo(stream);
92
93                    String base64 = Base64.encodeToString(stream.toByteArray(), Base64.NO_WRAP);
94                    builder.insert(0, "echo '" + base64 + "' | base64 -d > /tmp/$$.bin; ");
95                    builder.append(" --data-binary @/tmp/$$.bin");
96                } else {
97                    builder.append(" [TOO MUCH DATA TO INCLUDE]");
98                }
99            }
100        }
101
102        return builder.toString();
103    }
104
105}
106