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 EasResponse(final HttpResponse response, final EmailClientConnectionManager connManager, final long reqTime)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 fromHttpRequest( EmailClientConnectionManager connManager, HttpClient client, HttpUriRequest request)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 isSuccess()87 public boolean isSuccess() { 88 return mStatus == HttpStatus.SC_OK; 89 } 90 isForbidden()91 public boolean isForbidden() { 92 return mStatus == HttpStatus.SC_FORBIDDEN; 93 } 94 95 /** 96 * @return Whether this response indicates an authentication error. 97 */ isAuthError()98 public boolean isAuthError() { 99 return mStatus == HttpStatus.SC_UNAUTHORIZED; 100 } 101 102 /** 103 * @return Whether this response indicates a provisioning error. 104 */ isProvisionError()105 public boolean isProvisionError() { 106 return (mStatus == HTTP_NEED_PROVISIONING) || isForbidden(); 107 } 108 109 /** 110 * @return Whether this response indicates a redirect error. 111 */ isRedirectError()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 */ isAuthError(int code)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 */ getRedirectAddress()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 */ getInputStream()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 isEmpty()167 public boolean isEmpty() { 168 return mLength == 0; 169 } 170 getStatus()171 public int getStatus() { 172 return mStatus; 173 } 174 isMissingCertificate()175 public boolean isMissingCertificate() { 176 return mClientCertRequested; 177 } 178 getHeader(String name)179 public Header getHeader(String name) { 180 return (mResponse == null) ? null : mResponse.getFirstHeader(name); 181 } 182 getLength()183 public int getLength() { 184 return mLength; 185 } 186 close()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 }