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