1498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo/*
2498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * Copyright (C) 2008-2009 Marc Blank
3498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * Licensed to The Android Open Source Project.
4498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo *
5498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * Licensed under the Apache License, Version 2.0 (the "License");
6498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * you may not use this file except in compliance with the License.
7498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * You may obtain a copy of the License at
8498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo *
9498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo *      http://www.apache.org/licenses/LICENSE-2.0
10498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo *
11498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * Unless required by applicable law or agreed to in writing, software
12498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * distributed under the License is distributed on an "AS IS" BASIS,
13498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * See the License for the specific language governing permissions and
15498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * limitations under the License.
16498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo */
17498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
18498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalopackage com.android.exchange;
19498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
203d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komaloimport com.android.emailcommon.utility.EmailClientConnectionManager;
210b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo
22498c903e02ef1b150d6dbd3a01d35839026db264Ben Komaloimport org.apache.http.Header;
23498c903e02ef1b150d6dbd3a01d35839026db264Ben Komaloimport org.apache.http.HttpEntity;
24498c903e02ef1b150d6dbd3a01d35839026db264Ben Komaloimport org.apache.http.HttpResponse;
250b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komaloimport org.apache.http.HttpStatus;
260b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komaloimport org.apache.http.client.HttpClient;
270b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komaloimport org.apache.http.client.methods.HttpUriRequest;
280b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo
29498c903e02ef1b150d6dbd3a01d35839026db264Ben Komaloimport java.io.IOException;
30498c903e02ef1b150d6dbd3a01d35839026db264Ben Komaloimport java.io.InputStream;
31498c903e02ef1b150d6dbd3a01d35839026db264Ben Komaloimport java.util.zip.GZIPInputStream;
32498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
33498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo/**
34498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo * Encapsulate a response to an HTTP POST
35498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo */
36498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalopublic class EasResponse {
376534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank    // MSFT's custom HTTP result code indicating the need to provision
386534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank    static private final int HTTP_NEED_PROVISIONING = 449;
396534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank
40498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    final HttpResponse mResponse;
41498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    private final HttpEntity mEntity;
42498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    private final int mLength;
43498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    private InputStream mInputStream;
44498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    private boolean mClosed;
45498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
460b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo    /**
470b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo     * Whether or not a certificate was requested by the server and missing.
480b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo     * If this is set, it is essentially a 403 whereby the failure was due
490b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo     */
500b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo    private boolean mClientCertRequested = false;
510b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo
520b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo    private EasResponse(HttpResponse response) {
53498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        mResponse = response;
540b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo        mEntity = (response == null) ? null : mResponse.getEntity();
55498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        if (mEntity !=  null) {
563d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo            mLength = (int) mEntity.getContentLength();
57498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        } else {
58498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo            mLength = 0;
59498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        }
60498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    }
61498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
623d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo    public static EasResponse fromHttpRequest(
633d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo            EmailClientConnectionManager connManager, HttpClient client, HttpUriRequest request)
640b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo            throws IOException {
653d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo
663d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo        long reqTime = System.currentTimeMillis();
673d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo        HttpResponse response = client.execute(request);
683d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo        EasResponse result = new EasResponse(response);
693d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo        if (isAuthError(response.getStatusLine().getStatusCode())
703d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo                && connManager.hasDetectedUnsatisfiedCertReq(reqTime)) {
710b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo            result.mClientCertRequested = true;
720b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo            result.mClosed = true;
730b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo        }
743d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo
753d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo        return result;
763d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo    }
773d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo
783d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo    /**
793d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo     * Determine whether an HTTP code represents an authentication error
803d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo     * @param code the HTTP code returned by the server
813d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo     * @return whether or not the code represents an authentication error
823d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo     */
833d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo    public static boolean isAuthError(int code) {
843d6d254c2503e317b9b0873dc6a638c974f705f4Ben Komalo        return (code == HttpStatus.SC_UNAUTHORIZED) || (code == HttpStatus.SC_FORBIDDEN);
850b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo    }
860b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo
87498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    /**
886534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank     * Determine whether an HTTP code represents a provisioning error
896534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank     * @param code the HTTP code returned by the server
906534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank     * @return whether or not the code represents an provisioning error
916534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank     */
926534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank    public static boolean isProvisionError(int code) {
936534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank        return (code == HTTP_NEED_PROVISIONING) || (code == HttpStatus.SC_FORBIDDEN);
946534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank    }
956534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank
966534bd5e100625c653c9fba09243bbb67a9023c7Marc Blank    /**
97498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo     * Return an appropriate input stream for the response, either a GZIPInputStream, for
98498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo     * compressed data, or a generic InputStream otherwise
99498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo     * @return the input stream for the response
100498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo     */
101498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    public InputStream getInputStream() {
102498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        if (mInputStream != null || mClosed) {
103498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo            throw new IllegalStateException("Can't reuse stream or get closed stream");
104498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        } else if (mEntity == null) {
105498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo            throw new IllegalStateException("Can't get input stream without entity");
106498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        }
107498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        InputStream is = null;
108498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        try {
109498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo            // Get the default input stream for the entity
110498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo            is = mEntity.getContent();
111498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo            Header ceHeader = mResponse.getFirstHeader("Content-Encoding");
112498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo            if (ceHeader != null) {
113498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo                String encoding = ceHeader.getValue();
114498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo                // If we're gzip encoded, wrap appropriately
115498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo                if (encoding.toLowerCase().equals("gzip")) {
116498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo                    is = new GZIPInputStream(is);
117498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo                }
118498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo            }
119498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        } catch (IllegalStateException e1) {
120498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        } catch (IOException e1) {
121498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        }
122498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        mInputStream = is;
123498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        return is;
124498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    }
125498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
126498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    public boolean isEmpty() {
127498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        return mLength == 0;
128498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    }
129498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
130498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    public int getStatus() {
1310b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo        return mClientCertRequested
1320b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo                ? HttpStatus.SC_UNAUTHORIZED
1330b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo                : mResponse.getStatusLine().getStatusCode();
1340b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo    }
1350b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo
1360b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo    public boolean isMissingCertificate() {
1370b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo        return mClientCertRequested;
138498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    }
139498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
140498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    public Header getHeader(String name) {
1410b3a1547b4adf380dab1cc9a1af6c227c9a4e00fBen Komalo        return (mResponse == null) ? null : mResponse.getFirstHeader(name);
142498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    }
143498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
144498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    public int getLength() {
145498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        return mLength;
146498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    }
147498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo
148498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    public void close() {
1496760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank        if (!mClosed) {
1506760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank            if (mEntity != null) {
1516760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                try {
1526760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                    mEntity.consumeContent();
1536760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                } catch (IOException e) {
1546760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                    // No harm, no foul
1556760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                }
1566760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank            }
1576760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank            if (mInputStream instanceof GZIPInputStream) {
1586760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                try {
1596760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                    mInputStream.close();
1606760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                } catch (IOException e) {
1616760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                    // We tried
1626760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank                }
163498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo            }
1646760c30a8218c4ab1d38774ed0d765384ca1189bMarc Blank            mClosed = true;
165498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo        }
166498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo    }
167498c903e02ef1b150d6dbd3a01d35839026db264Ben Komalo}