1 /* 2 * Copyright (C) 2008 Esmertec AG. 3 * Copyright (C) 2008 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.mms.transaction; 19 20 import java.io.DataInputStream; 21 import java.io.IOException; 22 import java.net.SocketException; 23 import java.net.URI; 24 import java.net.URISyntaxException; 25 import java.util.Locale; 26 27 import org.apache.http.HttpEntity; 28 import org.apache.http.HttpHost; 29 import org.apache.http.HttpRequest; 30 import org.apache.http.HttpResponse; 31 import org.apache.http.StatusLine; 32 import org.apache.http.client.methods.HttpGet; 33 import org.apache.http.client.methods.HttpPost; 34 import org.apache.http.conn.params.ConnRouteParams; 35 import org.apache.http.params.HttpConnectionParams; 36 import org.apache.http.params.HttpParams; 37 import org.apache.http.params.HttpProtocolParams; 38 39 import android.content.Context; 40 import android.net.http.AndroidHttpClient; 41 import android.telephony.TelephonyManager; 42 import android.text.TextUtils; 43 import android.util.Config; 44 import android.util.Log; 45 46 import com.android.mms.LogTag; 47 import com.android.mms.MmsConfig; 48 49 public class HttpUtils { 50 private static final String TAG = LogTag.TRANSACTION; 51 52 private static final boolean DEBUG = false; 53 private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; 54 55 public static final int HTTP_POST_METHOD = 1; 56 public static final int HTTP_GET_METHOD = 2; 57 58 private static final int MMS_READ_BUFFER = 4096; 59 60 // This is the value to use for the "Accept-Language" header. 61 // Once it becomes possible for the user to change the locale 62 // setting, this should no longer be static. We should call 63 // getHttpAcceptLanguage instead. 64 private static final String HDR_VALUE_ACCEPT_LANGUAGE; 65 66 static { 67 HDR_VALUE_ACCEPT_LANGUAGE = getCurrentAcceptLanguage(Locale.getDefault()); 68 } 69 70 // Definition for necessary HTTP headers. 71 private static final String HDR_KEY_ACCEPT = "Accept"; 72 private static final String HDR_KEY_ACCEPT_LANGUAGE = "Accept-Language"; 73 74 private static final String HDR_VALUE_ACCEPT = 75 "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"; 76 HttpUtils()77 private HttpUtils() { 78 // To forbidden instantiate this class. 79 } 80 81 /** 82 * A helper method to send or retrieve data through HTTP protocol. 83 * 84 * @param token The token to identify the sending progress. 85 * @param url The URL used in a GET request. Null when the method is 86 * HTTP_POST_METHOD. 87 * @param pdu The data to be POST. Null when the method is HTTP_GET_METHOD. 88 * @param method HTTP_POST_METHOD or HTTP_GET_METHOD. 89 * @return A byte array which contains the response data. 90 * If an HTTP error code is returned, an IOException will be thrown. 91 * @throws IOException if any error occurred on network interface or 92 * an HTTP error code(>=400) returned from the server. 93 */ httpConnection(Context context, long token, String url, byte[] pdu, int method, boolean isProxySet, String proxyHost, int proxyPort)94 protected static byte[] httpConnection(Context context, long token, 95 String url, byte[] pdu, int method, boolean isProxySet, 96 String proxyHost, int proxyPort) throws IOException { 97 if (url == null) { 98 throw new IllegalArgumentException("URL must not be null."); 99 } 100 101 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 102 Log.v(TAG, "httpConnection: params list"); 103 Log.v(TAG, "\ttoken\t\t= " + token); 104 Log.v(TAG, "\turl\t\t= " + url); 105 Log.v(TAG, "\tmethod\t\t= " 106 + ((method == HTTP_POST_METHOD) ? "POST" 107 : ((method == HTTP_GET_METHOD) ? "GET" : "UNKNOWN"))); 108 Log.v(TAG, "\tisProxySet\t= " + isProxySet); 109 Log.v(TAG, "\tproxyHost\t= " + proxyHost); 110 Log.v(TAG, "\tproxyPort\t= " + proxyPort); 111 // TODO Print out binary data more readable. 112 //Log.v(TAG, "\tpdu\t\t= " + Arrays.toString(pdu)); 113 } 114 115 AndroidHttpClient client = null; 116 117 try { 118 // Make sure to use a proxy which supports CONNECT. 119 URI hostUrl = new URI(url); 120 HttpHost target = new HttpHost( 121 hostUrl.getHost(), hostUrl.getPort(), 122 HttpHost.DEFAULT_SCHEME_NAME); 123 124 client = createHttpClient(context); 125 HttpRequest req = null; 126 switch(method) { 127 case HTTP_POST_METHOD: 128 ProgressCallbackEntity entity = new ProgressCallbackEntity( 129 context, token, pdu); 130 // Set request content type. 131 entity.setContentType("application/vnd.wap.mms-message"); 132 133 HttpPost post = new HttpPost(url); 134 post.setEntity(entity); 135 req = post; 136 break; 137 case HTTP_GET_METHOD: 138 req = new HttpGet(url); 139 break; 140 default: 141 Log.e(TAG, "Unknown HTTP method: " + method 142 + ". Must be one of POST[" + HTTP_POST_METHOD 143 + "] or GET[" + HTTP_GET_METHOD + "]."); 144 return null; 145 } 146 147 // Set route parameters for the request. 148 HttpParams params = client.getParams(); 149 if (isProxySet) { 150 ConnRouteParams.setDefaultProxy( 151 params, new HttpHost(proxyHost, proxyPort)); 152 } 153 req.setParams(params); 154 155 // Set necessary HTTP headers for MMS transmission. 156 req.addHeader(HDR_KEY_ACCEPT, HDR_VALUE_ACCEPT); 157 { 158 String xWapProfileTagName = MmsConfig.getUaProfTagName(); 159 String xWapProfileUrl = MmsConfig.getUaProfUrl(); 160 161 if (xWapProfileUrl != null) { 162 if (Log.isLoggable(LogTag.TRANSACTION, Log.VERBOSE)) { 163 Log.d(LogTag.TRANSACTION, 164 "[HttpUtils] httpConn: xWapProfUrl=" + xWapProfileUrl); 165 } 166 req.addHeader(xWapProfileTagName, xWapProfileUrl); 167 } 168 } 169 170 // Extra http parameters. Split by '|' to get a list of value pairs. 171 // Separate each pair by the first occurrence of ':' to obtain a name and 172 // value. Replace the occurrence of the string returned by 173 // MmsConfig.getHttpParamsLine1Key() with the users telephone number inside 174 // the value. 175 String extraHttpParams = MmsConfig.getHttpParams(); 176 177 if (extraHttpParams != null) { 178 String line1Number = ((TelephonyManager)context 179 .getSystemService(Context.TELEPHONY_SERVICE)) 180 .getLine1Number(); 181 String line1Key = MmsConfig.getHttpParamsLine1Key(); 182 String paramList[] = extraHttpParams.split("\\|"); 183 184 for (String paramPair : paramList) { 185 String splitPair[] = paramPair.split(":", 2); 186 187 if (splitPair.length == 2) { 188 String name = splitPair[0].trim(); 189 String value = splitPair[1].trim(); 190 191 if (line1Key != null) { 192 value = value.replace(line1Key, line1Number); 193 } 194 if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(value)) { 195 req.addHeader(name, value); 196 } 197 } 198 } 199 } 200 req.addHeader(HDR_KEY_ACCEPT_LANGUAGE, HDR_VALUE_ACCEPT_LANGUAGE); 201 202 HttpResponse response = client.execute(target, req); 203 StatusLine status = response.getStatusLine(); 204 if (status.getStatusCode() != 200) { // HTTP 200 is success. 205 throw new IOException("HTTP error: " + status.getReasonPhrase()); 206 } 207 208 HttpEntity entity = response.getEntity(); 209 byte[] body = null; 210 if (entity != null) { 211 try { 212 if (entity.getContentLength() > 0) { 213 body = new byte[(int) entity.getContentLength()]; 214 DataInputStream dis = new DataInputStream(entity.getContent()); 215 try { 216 dis.readFully(body); 217 } finally { 218 try { 219 dis.close(); 220 } catch (IOException e) { 221 Log.e(TAG, "Error closing input stream: " + e.getMessage()); 222 } 223 } 224 } 225 if (entity.isChunked()) { 226 Log.v(TAG, "httpConnection: transfer encoding is chunked"); 227 int bytesTobeRead = MmsConfig.getMaxMessageSize(); 228 byte[] tempBody = new byte[bytesTobeRead]; 229 DataInputStream dis = new DataInputStream(entity.getContent()); 230 try { 231 int bytesRead = 0; 232 int offset = 0; 233 boolean readError = false; 234 do { 235 try { 236 bytesRead = dis.read(tempBody, offset, bytesTobeRead); 237 } catch (IOException e) { 238 readError = true; 239 Log.e(TAG, "httpConnection: error reading input stream" 240 + e.getMessage()); 241 break; 242 } 243 if (bytesRead > 0) { 244 bytesTobeRead -= bytesRead; 245 offset += bytesRead; 246 } 247 } while (bytesRead >= 0 && bytesTobeRead > 0); 248 if (bytesRead == -1 && offset > 0 && !readError) { 249 // offset is same as total number of bytes read 250 // bytesRead will be -1 if the data was read till the eof 251 body = new byte[offset]; 252 System.arraycopy(tempBody, 0, body, 0, offset); 253 Log.v(TAG, "httpConnection: Chunked response length [" 254 + Integer.toString(offset) + "]"); 255 } else { 256 Log.e(TAG, "httpConnection: Response entity too large or empty"); 257 } 258 } finally { 259 try { 260 dis.close(); 261 } catch (IOException e) { 262 Log.e(TAG, "Error closing input stream: " + e.getMessage()); 263 } 264 } 265 } 266 } finally { 267 if (entity != null) { 268 entity.consumeContent(); 269 } 270 } 271 } 272 return body; 273 } catch (URISyntaxException e) { 274 handleHttpConnectionException(e, url); 275 } catch (IllegalStateException e) { 276 handleHttpConnectionException(e, url); 277 } catch (IllegalArgumentException e) { 278 handleHttpConnectionException(e, url); 279 } catch (SocketException e) { 280 handleHttpConnectionException(e, url); 281 } catch (Exception e) { 282 handleHttpConnectionException(e, url); 283 } 284 finally { 285 if (client != null) { 286 client.close(); 287 } 288 } 289 return null; 290 } 291 handleHttpConnectionException(Exception exception, String url)292 private static void handleHttpConnectionException(Exception exception, String url) 293 throws IOException { 294 // Inner exception should be logged to make life easier. 295 Log.e(TAG, "Url: " + url + "\n" + exception.getMessage()); 296 IOException e = new IOException(exception.getMessage()); 297 e.initCause(exception); 298 throw e; 299 } 300 createHttpClient(Context context)301 private static AndroidHttpClient createHttpClient(Context context) { 302 String userAgent = MmsConfig.getUserAgent(); 303 AndroidHttpClient client = AndroidHttpClient.newInstance(userAgent, context); 304 HttpParams params = client.getParams(); 305 HttpProtocolParams.setContentCharset(params, "UTF-8"); 306 307 // set the socket timeout 308 int soTimeout = MmsConfig.getHttpSocketTimeout(); 309 310 if (Log.isLoggable(LogTag.TRANSACTION, Log.DEBUG)) { 311 Log.d(TAG, "[HttpUtils] createHttpClient w/ socket timeout " + soTimeout + " ms, " 312 + ", UA=" + userAgent); 313 } 314 HttpConnectionParams.setSoTimeout(params, soTimeout); 315 return client; 316 } 317 318 private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US"; 319 320 /** 321 * Return the Accept-Language header. Use the current locale plus 322 * US if we are in a different locale than US. 323 * This code copied from the browser's WebSettings.java 324 * @return Current AcceptLanguage String. 325 */ getCurrentAcceptLanguage(Locale locale)326 public static String getCurrentAcceptLanguage(Locale locale) { 327 StringBuilder buffer = new StringBuilder(); 328 addLocaleToHttpAcceptLanguage(buffer, locale); 329 330 if (!Locale.US.equals(locale)) { 331 if (buffer.length() > 0) { 332 buffer.append(", "); 333 } 334 buffer.append(ACCEPT_LANG_FOR_US_LOCALE); 335 } 336 337 return buffer.toString(); 338 } 339 340 /** 341 * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish, 342 * to new standard. 343 */ convertObsoleteLanguageCodeToNew(String langCode)344 private static String convertObsoleteLanguageCodeToNew(String langCode) { 345 if (langCode == null) { 346 return null; 347 } 348 if ("iw".equals(langCode)) { 349 // Hebrew 350 return "he"; 351 } else if ("in".equals(langCode)) { 352 // Indonesian 353 return "id"; 354 } else if ("ji".equals(langCode)) { 355 // Yiddish 356 return "yi"; 357 } 358 return langCode; 359 } 360 addLocaleToHttpAcceptLanguage(StringBuilder builder, Locale locale)361 private static void addLocaleToHttpAcceptLanguage(StringBuilder builder, 362 Locale locale) { 363 String language = convertObsoleteLanguageCodeToNew(locale.getLanguage()); 364 if (language != null) { 365 builder.append(language); 366 String country = locale.getCountry(); 367 if (country != null) { 368 builder.append("-"); 369 builder.append(country); 370 } 371 } 372 } 373 } 374