Home | History | Annotate | Download | only in exchange
      1 /*
      2  * Copyright (C) 2008-2009 Marc Blank
      3  * Licensed to The Android Open Source Project.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 
     18 package com.android.exchange;
     19 
     20 import android.net.Uri;
     21 
     22 import com.android.emailcommon.utility.EmailClientConnectionManager;
     23 
     24 import org.apache.http.Header;
     25 import org.apache.http.HttpEntity;
     26 import org.apache.http.HttpResponse;
     27 import org.apache.http.HttpStatus;
     28 import org.apache.http.client.HttpClient;
     29 import org.apache.http.client.methods.HttpUriRequest;
     30 
     31 import java.io.IOException;
     32 import java.io.InputStream;
     33 import java.util.zip.GZIPInputStream;
     34 
     35 /**
     36  * Encapsulate a response to an HTTP POST
     37  */
     38 public class EasResponse {
     39     // MSFT's custom HTTP result code indicating the need to provision
     40     static private final int HTTP_NEED_PROVISIONING = 449;
     41 
     42     // Microsoft-defined HTTP response indicating a redirect to a "better" server.
     43     // Why is this a 4xx instead of 3xx? Because EAS considers this a "Device misconfigured" error.
     44     static private final int HTTP_REDIRECT = 451;
     45 
     46     private final HttpResponse mResponse;
     47     private final HttpEntity mEntity;
     48     private final int mLength;
     49     private InputStream mInputStream;
     50     private boolean mClosed;
     51 
     52     private final int mStatus;
     53 
     54     /**
     55      * Whether or not a certificate was requested by the server and missing.
     56      * If this is set, it is essentially a 403 whereby the failure was due
     57      */
     58     private final boolean mClientCertRequested;
     59 
     60     private EasResponse(final HttpResponse response,
     61             final EmailClientConnectionManager connManager, final long reqTime) {
     62         mResponse = response;
     63         mEntity = (response == null) ? null : mResponse.getEntity();
     64         if (mEntity !=  null) {
     65             mLength = (int) mEntity.getContentLength();
     66         } else {
     67             mLength = 0;
     68         }
     69         int status = response.getStatusLine().getStatusCode();
     70         mClientCertRequested =
     71                 isAuthError(status) && connManager.hasDetectedUnsatisfiedCertReq(reqTime);
     72         if (mClientCertRequested) {
     73             status = HttpStatus.SC_UNAUTHORIZED;
     74             mClosed = true;
     75         }
     76         mStatus = status;
     77     }
     78 
     79     public static EasResponse fromHttpRequest(
     80             EmailClientConnectionManager connManager, HttpClient client, HttpUriRequest request)
     81             throws IOException {
     82         final long reqTime = System.currentTimeMillis();
     83         final HttpResponse response = client.execute(request);
     84         return new EasResponse(response, connManager, reqTime);
     85     }
     86 
     87     public boolean isSuccess() {
     88         return mStatus == HttpStatus.SC_OK;
     89     }
     90 
     91     public boolean isForbidden() {
     92         return mStatus == HttpStatus.SC_FORBIDDEN;
     93     }
     94 
     95     /**
     96      * @return Whether this response indicates an authentication error.
     97      */
     98     public boolean isAuthError() {
     99         return mStatus == HttpStatus.SC_UNAUTHORIZED;
    100     }
    101 
    102     /**
    103      * @return Whether this response indicates a provisioning error.
    104      */
    105     public boolean isProvisionError() {
    106         return (mStatus == HTTP_NEED_PROVISIONING) || isForbidden();
    107     }
    108 
    109     /**
    110      * @return Whether this response indicates a redirect error.
    111      */
    112     public boolean isRedirectError() {
    113         return mStatus == HTTP_REDIRECT;
    114     }
    115 
    116     /**
    117      * Determine whether an HTTP code represents an authentication error
    118      * @param code the HTTP code returned by the server
    119      * @return whether or not the code represents an authentication error
    120      */
    121     private static boolean isAuthError(int code) {
    122         return (code == HttpStatus.SC_UNAUTHORIZED) || (code == HttpStatus.SC_FORBIDDEN);
    123     }
    124 
    125     /**
    126      * Read the redirect address from this response, if it's present.
    127      * @return The new host address, or null if it's not there.
    128      */
    129     public String getRedirectAddress() {
    130         final Header locHeader = getHeader("X-MS-Location");
    131         if (locHeader != null) {
    132             return Uri.parse(locHeader.getValue()).getHost();
    133         }
    134         return null;
    135     }
    136 
    137     /**
    138      * Return an appropriate input stream for the response, either a GZIPInputStream, for
    139      * compressed data, or a generic InputStream otherwise
    140      * @return the input stream for the response
    141      */
    142     public InputStream getInputStream() {
    143         if (mInputStream != null || mClosed) {
    144             throw new IllegalStateException("Can't reuse stream or get closed stream");
    145         } else if (mEntity == null) {
    146             throw new IllegalStateException("Can't get input stream without entity");
    147         }
    148         InputStream is = null;
    149         try {
    150             // Get the default input stream for the entity
    151             is = mEntity.getContent();
    152             Header ceHeader = mResponse.getFirstHeader("Content-Encoding");
    153             if (ceHeader != null) {
    154                 String encoding = ceHeader.getValue();
    155                 // If we're gzip encoded, wrap appropriately
    156                 if (encoding.toLowerCase().equals("gzip")) {
    157                     is = new GZIPInputStream(is);
    158                 }
    159             }
    160         } catch (IllegalStateException e1) {
    161         } catch (IOException e1) {
    162         }
    163         mInputStream = is;
    164         return is;
    165     }
    166 
    167     public boolean isEmpty() {
    168         return mLength == 0;
    169     }
    170 
    171     public int getStatus() {
    172         return mStatus;
    173     }
    174 
    175     public boolean isMissingCertificate() {
    176         return mClientCertRequested;
    177     }
    178 
    179     public Header getHeader(String name) {
    180         return (mResponse == null) ? null : mResponse.getFirstHeader(name);
    181     }
    182 
    183     public int getLength() {
    184         return mLength;
    185     }
    186 
    187     public void close() {
    188         if (!mClosed) {
    189             if (mEntity != null) {
    190                 try {
    191                     mEntity.consumeContent();
    192                 } catch (IOException e) {
    193                     // No harm, no foul
    194                 }
    195             }
    196             if (mInputStream instanceof GZIPInputStream) {
    197                 try {
    198                     mInputStream.close();
    199                 } catch (IOException e) {
    200                     // We tried
    201                 }
    202             }
    203             mClosed = true;
    204         }
    205     }
    206 }