Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2014 The Android Open Source Project |
| 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.mms.service; |
| 18 | |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 19 | import android.content.Context; |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 20 | import android.net.ConnectivityManager; |
| 21 | import android.net.LinkProperties; |
Ye Wen | 7a284e5 | 2015-01-26 15:17:24 -0800 | [diff] [blame] | 22 | import android.net.Network; |
Remi NGUYEN VAN | 2a83a31 | 2018-04-25 17:53:28 +0900 | [diff] [blame] | 23 | import android.net.dns.ResolvUtil; |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 24 | import android.os.Bundle; |
Ye Wen | 44108ec | 2016-07-18 14:37:34 -0700 | [diff] [blame] | 25 | import android.telephony.CarrierConfigManager; |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 26 | import android.telephony.SmsManager; |
| 27 | import android.telephony.SubscriptionManager; |
| 28 | import android.telephony.TelephonyManager; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 29 | import android.text.TextUtils; |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 30 | import android.util.Base64; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 31 | import android.util.Log; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 32 | import com.android.mms.service.exception.MmsHttpException; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 33 | |
| 34 | import java.io.BufferedInputStream; |
| 35 | import java.io.BufferedOutputStream; |
| 36 | import java.io.ByteArrayOutputStream; |
| 37 | import java.io.IOException; |
| 38 | import java.io.InputStream; |
| 39 | import java.io.OutputStream; |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 40 | import java.io.UnsupportedEncodingException; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 41 | import java.net.HttpURLConnection; |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 42 | import java.net.Inet4Address; |
| 43 | import java.net.InetAddress; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 44 | import java.net.InetSocketAddress; |
| 45 | import java.net.MalformedURLException; |
| 46 | import java.net.ProtocolException; |
| 47 | import java.net.Proxy; |
| 48 | import java.net.URL; |
| 49 | import java.util.List; |
| 50 | import java.util.Locale; |
| 51 | import java.util.Map; |
| 52 | import java.util.regex.Matcher; |
| 53 | import java.util.regex.Pattern; |
| 54 | |
| 55 | /** |
| 56 | * MMS HTTP client for sending and downloading MMS messages |
| 57 | */ |
| 58 | public class MmsHttpClient { |
| 59 | public static final String METHOD_POST = "POST"; |
| 60 | public static final String METHOD_GET = "GET"; |
| 61 | |
| 62 | private static final String HEADER_CONTENT_TYPE = "Content-Type"; |
| 63 | private static final String HEADER_ACCEPT = "Accept"; |
| 64 | private static final String HEADER_ACCEPT_LANGUAGE = "Accept-Language"; |
| 65 | private static final String HEADER_USER_AGENT = "User-Agent"; |
Ye Wen | 44108ec | 2016-07-18 14:37:34 -0700 | [diff] [blame] | 66 | private static final String HEADER_CONNECTION = "Connection"; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 67 | |
| 68 | // The "Accept" header value |
| 69 | private static final String HEADER_VALUE_ACCEPT = |
| 70 | "*/*, application/vnd.wap.mms-message, application/vnd.wap.sic"; |
| 71 | // The "Content-Type" header value |
Ye Wen | 857fb76 | 2014-12-15 14:44:22 -0800 | [diff] [blame] | 72 | private static final String HEADER_VALUE_CONTENT_TYPE_WITH_CHARSET = |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 73 | "application/vnd.wap.mms-message; charset=utf-8"; |
Ye Wen | 857fb76 | 2014-12-15 14:44:22 -0800 | [diff] [blame] | 74 | private static final String HEADER_VALUE_CONTENT_TYPE_WITHOUT_CHARSET = |
| 75 | "application/vnd.wap.mms-message"; |
Ye Wen | 44108ec | 2016-07-18 14:37:34 -0700 | [diff] [blame] | 76 | private static final String HEADER_CONNECTION_CLOSE = "close"; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 77 | |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 78 | private static final int IPV4_WAIT_ATTEMPTS = 15; |
| 79 | private static final long IPV4_WAIT_DELAY_MS = 1000; // 1 seconds |
| 80 | |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 81 | private final Context mContext; |
Neil Fuller | 6b2ef3b | 2015-01-20 15:39:29 +0000 | [diff] [blame] | 82 | private final Network mNetwork; |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 83 | private final ConnectivityManager mConnectivityManager; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 84 | |
| 85 | /** |
| 86 | * Constructor |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 87 | * @param context The Context object |
Neil Fuller | 6b2ef3b | 2015-01-20 15:39:29 +0000 | [diff] [blame] | 88 | * @param network The Network for creating an OKHttp client |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 89 | * @param connectivityManager |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 90 | */ |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 91 | public MmsHttpClient(Context context, Network network, |
| 92 | ConnectivityManager connectivityManager) { |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 93 | mContext = context; |
Remi NGUYEN VAN | 2a83a31 | 2018-04-25 17:53:28 +0900 | [diff] [blame] | 94 | // Mms server is on a carrier private network so it may not be resolvable using 3rd party |
| 95 | // private dns |
| 96 | mNetwork = ResolvUtil.makeNetworkWithPrivateDnsBypass(network); |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 97 | mConnectivityManager = connectivityManager; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 98 | } |
| 99 | |
| 100 | /** |
| 101 | * Execute an MMS HTTP request, either a POST (sending) or a GET (downloading) |
| 102 | * |
| 103 | * @param urlString The request URL, for sending it is usually the MMSC, and for downloading |
| 104 | * it is the message URL |
| 105 | * @param pdu For POST (sending) only, the PDU to send |
| 106 | * @param method HTTP method, POST for sending and GET for downloading |
| 107 | * @param isProxySet Is there a proxy for the MMSC |
| 108 | * @param proxyHost The proxy host |
| 109 | * @param proxyPort The proxy port |
| 110 | * @param mmsConfig The MMS config to use |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 111 | * @param subId The subscription ID used to get line number, etc. |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 112 | * @param requestId The request ID for logging |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 113 | * @return The HTTP response body |
| 114 | * @throws MmsHttpException For any failures |
| 115 | */ |
| 116 | public byte[] execute(String urlString, byte[] pdu, String method, boolean isProxySet, |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 117 | String proxyHost, int proxyPort, Bundle mmsConfig, int subId, String requestId) |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 118 | throws MmsHttpException { |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 119 | LogUtil.d(requestId, "HTTP: " + method + " " + redactUrlForNonVerbose(urlString) |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 120 | + (isProxySet ? (", proxy=" + proxyHost + ":" + proxyPort) : "") |
| 121 | + ", PDU size=" + (pdu != null ? pdu.length : 0)); |
| 122 | checkMethod(method); |
| 123 | HttpURLConnection connection = null; |
| 124 | try { |
Ye Wen | 960d2d4 | 2015-02-27 13:10:31 -0800 | [diff] [blame] | 125 | Proxy proxy = Proxy.NO_PROXY; |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 126 | if (isProxySet) { |
Ye Wen | c2348f8 | 2015-09-17 11:28:31 -0700 | [diff] [blame] | 127 | proxy = new Proxy(Proxy.Type.HTTP, |
| 128 | new InetSocketAddress(mNetwork.getByName(proxyHost), proxyPort)); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 129 | } |
| 130 | final URL url = new URL(urlString); |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 131 | maybeWaitForIpv4(requestId, url); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 132 | // Now get the connection |
Ye Wen | 7a284e5 | 2015-01-26 15:17:24 -0800 | [diff] [blame] | 133 | connection = (HttpURLConnection) mNetwork.openConnection(url, proxy); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 134 | connection.setDoInput(true); |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 135 | connection.setConnectTimeout( |
| 136 | mmsConfig.getInt(SmsManager.MMS_CONFIG_HTTP_SOCKET_TIMEOUT)); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 137 | // ------- COMMON HEADERS --------- |
| 138 | // Header: Accept |
| 139 | connection.setRequestProperty(HEADER_ACCEPT, HEADER_VALUE_ACCEPT); |
| 140 | // Header: Accept-Language |
| 141 | connection.setRequestProperty( |
| 142 | HEADER_ACCEPT_LANGUAGE, getCurrentAcceptLanguage(Locale.getDefault())); |
| 143 | // Header: User-Agent |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 144 | final String userAgent = mmsConfig.getString(SmsManager.MMS_CONFIG_USER_AGENT); |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 145 | LogUtil.i(requestId, "HTTP: User-Agent=" + userAgent); |
Ye Wen | f3debb1 | 2014-11-26 11:12:13 -0800 | [diff] [blame] | 146 | connection.setRequestProperty(HEADER_USER_AGENT, userAgent); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 147 | // Header: x-wap-profile |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 148 | final String uaProfUrlTagName = |
| 149 | mmsConfig.getString(SmsManager.MMS_CONFIG_UA_PROF_TAG_NAME); |
| 150 | final String uaProfUrl = mmsConfig.getString(SmsManager.MMS_CONFIG_UA_PROF_URL); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 151 | if (uaProfUrl != null) { |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 152 | LogUtil.i(requestId, "HTTP: UaProfUrl=" + uaProfUrl); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 153 | connection.setRequestProperty(uaProfUrlTagName, uaProfUrl); |
| 154 | } |
Ye Wen | 44108ec | 2016-07-18 14:37:34 -0700 | [diff] [blame] | 155 | // Header: Connection: close (if needed) |
| 156 | // Some carriers require that the HTTP connection's socket is closed |
| 157 | // after an MMS request/response is complete. In these cases keep alive |
| 158 | // is disabled. See https://tools.ietf.org/html/rfc7230#section-6.6 |
| 159 | if (mmsConfig.getBoolean(SmsManager.MMS_CONFIG_CLOSE_CONNECTION, false)) { |
| 160 | LogUtil.i(requestId, "HTTP: Connection close after request"); |
| 161 | connection.setRequestProperty(HEADER_CONNECTION, HEADER_CONNECTION_CLOSE); |
| 162 | } |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 163 | // Add extra headers specified by mms_config.xml's httpparams |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 164 | addExtraHeaders(connection, mmsConfig, subId); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 165 | // Different stuff for GET and POST |
| 166 | if (METHOD_POST.equals(method)) { |
| 167 | if (pdu == null || pdu.length < 1) { |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 168 | LogUtil.e(requestId, "HTTP: empty pdu"); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 169 | throw new MmsHttpException(0/*statusCode*/, "Sending empty PDU"); |
| 170 | } |
| 171 | connection.setDoOutput(true); |
| 172 | connection.setRequestMethod(METHOD_POST); |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 173 | if (mmsConfig.getBoolean(SmsManager.MMS_CONFIG_SUPPORT_HTTP_CHARSET_HEADER)) { |
Ye Wen | 857fb76 | 2014-12-15 14:44:22 -0800 | [diff] [blame] | 174 | connection.setRequestProperty(HEADER_CONTENT_TYPE, |
| 175 | HEADER_VALUE_CONTENT_TYPE_WITH_CHARSET); |
| 176 | } else { |
| 177 | connection.setRequestProperty(HEADER_CONTENT_TYPE, |
| 178 | HEADER_VALUE_CONTENT_TYPE_WITHOUT_CHARSET); |
| 179 | } |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 180 | if (LogUtil.isLoggable(Log.VERBOSE)) { |
| 181 | logHttpHeaders(connection.getRequestProperties(), requestId); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 182 | } |
| 183 | connection.setFixedLengthStreamingMode(pdu.length); |
| 184 | // Sending request body |
| 185 | final OutputStream out = |
| 186 | new BufferedOutputStream(connection.getOutputStream()); |
| 187 | out.write(pdu); |
| 188 | out.flush(); |
| 189 | out.close(); |
| 190 | } else if (METHOD_GET.equals(method)) { |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 191 | if (LogUtil.isLoggable(Log.VERBOSE)) { |
| 192 | logHttpHeaders(connection.getRequestProperties(), requestId); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 193 | } |
| 194 | connection.setRequestMethod(METHOD_GET); |
| 195 | } |
| 196 | // Get response |
Ye Wen | f3debb1 | 2014-11-26 11:12:13 -0800 | [diff] [blame] | 197 | final int responseCode = connection.getResponseCode(); |
| 198 | final String responseMessage = connection.getResponseMessage(); |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 199 | LogUtil.d(requestId, "HTTP: " + responseCode + " " + responseMessage); |
| 200 | if (LogUtil.isLoggable(Log.VERBOSE)) { |
| 201 | logHttpHeaders(connection.getHeaderFields(), requestId); |
Ye Wen | f3debb1 | 2014-11-26 11:12:13 -0800 | [diff] [blame] | 202 | } |
| 203 | if (responseCode / 100 != 2) { |
| 204 | throw new MmsHttpException(responseCode, responseMessage); |
| 205 | } |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 206 | final InputStream in = new BufferedInputStream(connection.getInputStream()); |
| 207 | final ByteArrayOutputStream byteOut = new ByteArrayOutputStream(); |
| 208 | final byte[] buf = new byte[4096]; |
| 209 | int count = 0; |
| 210 | while ((count = in.read(buf)) > 0) { |
| 211 | byteOut.write(buf, 0, count); |
| 212 | } |
| 213 | in.close(); |
| 214 | final byte[] responseBody = byteOut.toByteArray(); |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 215 | LogUtil.d(requestId, "HTTP: response size=" |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 216 | + (responseBody != null ? responseBody.length : 0)); |
| 217 | return responseBody; |
| 218 | } catch (MalformedURLException e) { |
Ye Wen | 189322a | 2015-02-19 16:08:16 -0800 | [diff] [blame] | 219 | final String redactedUrl = redactUrlForNonVerbose(urlString); |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 220 | LogUtil.e(requestId, "HTTP: invalid URL " + redactedUrl, e); |
Ye Wen | 189322a | 2015-02-19 16:08:16 -0800 | [diff] [blame] | 221 | throw new MmsHttpException(0/*statusCode*/, "Invalid URL " + redactedUrl, e); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 222 | } catch (ProtocolException e) { |
Ye Wen | 189322a | 2015-02-19 16:08:16 -0800 | [diff] [blame] | 223 | final String redactedUrl = redactUrlForNonVerbose(urlString); |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 224 | LogUtil.e(requestId, "HTTP: invalid URL protocol " + redactedUrl, e); |
Ye Wen | 189322a | 2015-02-19 16:08:16 -0800 | [diff] [blame] | 225 | throw new MmsHttpException(0/*statusCode*/, "Invalid URL protocol " + redactedUrl, e); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 226 | } catch (IOException e) { |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 227 | LogUtil.e(requestId, "HTTP: IO failure", e); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 228 | throw new MmsHttpException(0/*statusCode*/, e); |
| 229 | } finally { |
| 230 | if (connection != null) { |
| 231 | connection.disconnect(); |
| 232 | } |
| 233 | } |
| 234 | } |
| 235 | |
Ye Wen | eeb364a | 2016-05-20 15:58:50 -0700 | [diff] [blame] | 236 | private void maybeWaitForIpv4(final String requestId, final URL url) { |
| 237 | // If it's a literal IPv4 address and we're on an IPv6-only network, |
| 238 | // wait until IPv4 is available. |
| 239 | Inet4Address ipv4Literal = null; |
| 240 | try { |
| 241 | ipv4Literal = (Inet4Address) InetAddress.parseNumericAddress(url.getHost()); |
| 242 | } catch (IllegalArgumentException | ClassCastException e) { |
| 243 | // Ignore |
| 244 | } |
| 245 | if (ipv4Literal == null) { |
| 246 | // Not an IPv4 address. |
| 247 | return; |
| 248 | } |
| 249 | for (int i = 0; i < IPV4_WAIT_ATTEMPTS; i++) { |
| 250 | final LinkProperties lp = mConnectivityManager.getLinkProperties(mNetwork); |
| 251 | if (lp != null) { |
| 252 | if (!lp.isReachable(ipv4Literal)) { |
| 253 | LogUtil.w(requestId, "HTTP: IPv4 not yet provisioned"); |
| 254 | try { |
| 255 | Thread.sleep(IPV4_WAIT_DELAY_MS); |
| 256 | } catch (InterruptedException e) { |
| 257 | // Ignore |
| 258 | } |
| 259 | } else { |
| 260 | LogUtil.i(requestId, "HTTP: IPv4 provisioned"); |
| 261 | break; |
| 262 | } |
| 263 | } else { |
| 264 | LogUtil.w(requestId, "HTTP: network disconnected, skip ipv4 check"); |
| 265 | break; |
| 266 | } |
| 267 | } |
| 268 | } |
| 269 | |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 270 | private static void logHttpHeaders(Map<String, List<String>> headers, String requestId) { |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 271 | final StringBuilder sb = new StringBuilder(); |
| 272 | if (headers != null) { |
| 273 | for (Map.Entry<String, List<String>> entry : headers.entrySet()) { |
| 274 | final String key = entry.getKey(); |
| 275 | final List<String> values = entry.getValue(); |
| 276 | if (values != null) { |
| 277 | for (String value : values) { |
| 278 | sb.append(key).append('=').append(value).append('\n'); |
| 279 | } |
| 280 | } |
| 281 | } |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 282 | LogUtil.v(requestId, "HTTP: headers\n" + sb.toString()); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 283 | } |
| 284 | } |
| 285 | |
| 286 | private static void checkMethod(String method) throws MmsHttpException { |
| 287 | if (!METHOD_GET.equals(method) && !METHOD_POST.equals(method)) { |
| 288 | throw new MmsHttpException(0/*statusCode*/, "Invalid method " + method); |
| 289 | } |
| 290 | } |
| 291 | |
| 292 | private static final String ACCEPT_LANG_FOR_US_LOCALE = "en-US"; |
| 293 | |
| 294 | /** |
| 295 | * Return the Accept-Language header. Use the current locale plus |
| 296 | * US if we are in a different locale than US. |
| 297 | * This code copied from the browser's WebSettings.java |
| 298 | * |
| 299 | * @return Current AcceptLanguage String. |
| 300 | */ |
| 301 | public static String getCurrentAcceptLanguage(Locale locale) { |
| 302 | final StringBuilder buffer = new StringBuilder(); |
| 303 | addLocaleToHttpAcceptLanguage(buffer, locale); |
| 304 | |
| 305 | if (!Locale.US.equals(locale)) { |
| 306 | if (buffer.length() > 0) { |
| 307 | buffer.append(", "); |
| 308 | } |
| 309 | buffer.append(ACCEPT_LANG_FOR_US_LOCALE); |
| 310 | } |
| 311 | |
| 312 | return buffer.toString(); |
| 313 | } |
| 314 | |
| 315 | /** |
| 316 | * Convert obsolete language codes, including Hebrew/Indonesian/Yiddish, |
| 317 | * to new standard. |
| 318 | */ |
| 319 | private static String convertObsoleteLanguageCodeToNew(String langCode) { |
| 320 | if (langCode == null) { |
| 321 | return null; |
| 322 | } |
| 323 | if ("iw".equals(langCode)) { |
| 324 | // Hebrew |
| 325 | return "he"; |
| 326 | } else if ("in".equals(langCode)) { |
| 327 | // Indonesian |
| 328 | return "id"; |
| 329 | } else if ("ji".equals(langCode)) { |
| 330 | // Yiddish |
| 331 | return "yi"; |
| 332 | } |
| 333 | return langCode; |
| 334 | } |
| 335 | |
| 336 | private static void addLocaleToHttpAcceptLanguage(StringBuilder builder, Locale locale) { |
| 337 | final String language = convertObsoleteLanguageCodeToNew(locale.getLanguage()); |
| 338 | if (language != null) { |
| 339 | builder.append(language); |
| 340 | final String country = locale.getCountry(); |
| 341 | if (country != null) { |
| 342 | builder.append("-"); |
| 343 | builder.append(country); |
| 344 | } |
| 345 | } |
| 346 | } |
| 347 | |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 348 | /** |
| 349 | * Add extra HTTP headers from mms_config.xml's httpParams, which is a list of key/value |
| 350 | * pairs separated by "|". Each key/value pair is separated by ":". Value may contain |
| 351 | * macros like "##LINE1##" or "##NAI##" which is resolved with methods in this class |
| 352 | * |
| 353 | * @param connection The HttpURLConnection that we add headers to |
| 354 | * @param mmsConfig The MmsConfig object |
| 355 | * @param subId The subscription ID used to get line number, etc. |
| 356 | */ |
| 357 | private void addExtraHeaders(HttpURLConnection connection, Bundle mmsConfig, int subId) { |
| 358 | final String extraHttpParams = mmsConfig.getString(SmsManager.MMS_CONFIG_HTTP_PARAMS); |
| 359 | if (!TextUtils.isEmpty(extraHttpParams)) { |
| 360 | // Parse the parameter list |
| 361 | String paramList[] = extraHttpParams.split("\\|"); |
| 362 | for (String paramPair : paramList) { |
| 363 | String splitPair[] = paramPair.split(":", 2); |
| 364 | if (splitPair.length == 2) { |
| 365 | final String name = splitPair[0].trim(); |
| 366 | final String value = |
| 367 | resolveMacro(mContext, splitPair[1].trim(), mmsConfig, subId); |
| 368 | if (!TextUtils.isEmpty(name) && !TextUtils.isEmpty(value)) { |
| 369 | // Add the header if the param is valid |
| 370 | connection.setRequestProperty(name, value); |
| 371 | } |
| 372 | } |
| 373 | } |
| 374 | } |
| 375 | } |
| 376 | |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 377 | private static final Pattern MACRO_P = Pattern.compile("##(\\S+)##"); |
| 378 | /** |
| 379 | * Resolve the macro in HTTP param value text |
| 380 | * For example, "something##LINE1##something" is resolved to "something9139531419something" |
| 381 | * |
| 382 | * @param value The HTTP param value possibly containing macros |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 383 | * @param subId The subscription ID used to get line number, etc. |
| 384 | * @return The HTTP param with macros resolved to real value |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 385 | */ |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 386 | private static String resolveMacro(Context context, String value, Bundle mmsConfig, int subId) { |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 387 | if (TextUtils.isEmpty(value)) { |
| 388 | return value; |
| 389 | } |
| 390 | final Matcher matcher = MACRO_P.matcher(value); |
| 391 | int nextStart = 0; |
| 392 | StringBuilder replaced = null; |
| 393 | while (matcher.find()) { |
| 394 | if (replaced == null) { |
| 395 | replaced = new StringBuilder(); |
| 396 | } |
| 397 | final int matchedStart = matcher.start(); |
| 398 | if (matchedStart > nextStart) { |
| 399 | replaced.append(value.substring(nextStart, matchedStart)); |
| 400 | } |
| 401 | final String macro = matcher.group(1); |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 402 | final String macroValue = getMacroValue(context, macro, mmsConfig, subId); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 403 | if (macroValue != null) { |
| 404 | replaced.append(macroValue); |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 405 | } |
| 406 | nextStart = matcher.end(); |
| 407 | } |
| 408 | if (replaced != null && nextStart < value.length()) { |
| 409 | replaced.append(value.substring(nextStart)); |
| 410 | } |
| 411 | return replaced == null ? value : replaced.toString(); |
| 412 | } |
| 413 | |
| 414 | /** |
Ye Wen | 189322a | 2015-02-19 16:08:16 -0800 | [diff] [blame] | 415 | * Redact the URL for non-VERBOSE logging. Replace url with only the host part and the length |
| 416 | * of the input URL string. |
| 417 | * |
| 418 | * @param urlString |
| 419 | * @return |
| 420 | */ |
| 421 | public static String redactUrlForNonVerbose(String urlString) { |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 422 | if (LogUtil.isLoggable(Log.VERBOSE)) { |
Ye Wen | 189322a | 2015-02-19 16:08:16 -0800 | [diff] [blame] | 423 | // Don't redact for VERBOSE level logging |
| 424 | return urlString; |
| 425 | } |
| 426 | if (TextUtils.isEmpty(urlString)) { |
| 427 | return urlString; |
| 428 | } |
| 429 | String protocol = "http"; |
| 430 | String host = ""; |
| 431 | try { |
| 432 | final URL url = new URL(urlString); |
| 433 | protocol = url.getProtocol(); |
| 434 | host = url.getHost(); |
| 435 | } catch (MalformedURLException e) { |
| 436 | // Ignore |
| 437 | } |
| 438 | // Print "http://host[length]" |
| 439 | final StringBuilder sb = new StringBuilder(); |
| 440 | sb.append(protocol).append("://").append(host) |
| 441 | .append("[").append(urlString.length()).append("]"); |
| 442 | return sb.toString(); |
| 443 | } |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 444 | |
| 445 | /* |
| 446 | * Macro names |
| 447 | */ |
| 448 | // The raw phone number from TelephonyManager.getLine1Number |
| 449 | private static final String MACRO_LINE1 = "LINE1"; |
| 450 | // The phone number without country code |
| 451 | private static final String MACRO_LINE1NOCOUNTRYCODE = "LINE1NOCOUNTRYCODE"; |
| 452 | // NAI (Network Access Identifier), used by Sprint for authentication |
| 453 | private static final String MACRO_NAI = "NAI"; |
| 454 | /** |
| 455 | * Return the HTTP param macro value. |
| 456 | * Example: "LINE1" returns the phone number, etc. |
| 457 | * |
| 458 | * @param macro The macro name |
Hotice Hsu | 1f4477d | 2019-01-07 13:31:30 +0800 | [diff] [blame^] | 459 | * @param mmsConfig The MMS config which contains NAI suffix and SIM country ISO to override. |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 460 | * @param subId The subscription ID used to get line number, etc. |
| 461 | * @return The value of the defined macro |
| 462 | */ |
| 463 | private static String getMacroValue(Context context, String macro, Bundle mmsConfig, |
| 464 | int subId) { |
| 465 | if (MACRO_LINE1.equals(macro)) { |
| 466 | return getLine1(context, subId); |
| 467 | } else if (MACRO_LINE1NOCOUNTRYCODE.equals(macro)) { |
Hotice Hsu | 1f4477d | 2019-01-07 13:31:30 +0800 | [diff] [blame^] | 468 | return getLine1NoCountryCode(context, mmsConfig, subId); |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 469 | } else if (MACRO_NAI.equals(macro)) { |
| 470 | return getNai(context, mmsConfig, subId); |
| 471 | } |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 472 | LogUtil.e("Invalid macro " + macro); |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 473 | return null; |
| 474 | } |
| 475 | |
| 476 | /** |
| 477 | * Returns the phone number for the given subscription ID. |
| 478 | */ |
| 479 | private static String getLine1(Context context, int subId) { |
| 480 | final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( |
| 481 | Context.TELEPHONY_SERVICE); |
Shishir Agrawal | 033ab5c | 2016-01-25 14:07:09 -0800 | [diff] [blame] | 482 | return telephonyManager.getLine1Number(subId); |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 483 | } |
| 484 | |
| 485 | /** |
| 486 | * Returns the phone number (without country code) for the given subscription ID. |
| 487 | */ |
Hotice Hsu | 1f4477d | 2019-01-07 13:31:30 +0800 | [diff] [blame^] | 488 | private static String getLine1NoCountryCode(Context context, Bundle mmsConfig, int subId) { |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 489 | final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( |
| 490 | Context.TELEPHONY_SERVICE); |
Hotice Hsu | 1f4477d | 2019-01-07 13:31:30 +0800 | [diff] [blame^] | 491 | String countryIsoOverride = |
| 492 | mmsConfig.getString(SmsManager.MMS_CONFIG_SIM_COUNTRY_ISO_OVERRIDE); |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 493 | return PhoneUtils.getNationalNumber( |
Hotice Hsu | 1f4477d | 2019-01-07 13:31:30 +0800 | [diff] [blame^] | 494 | telephonyManager, |
| 495 | subId, |
| 496 | telephonyManager.getLine1Number(subId), |
| 497 | countryIsoOverride); |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 498 | } |
| 499 | |
| 500 | /** |
| 501 | * Returns the NAI (Network Access Identifier) from SystemProperties for the given subscription |
| 502 | * ID. |
| 503 | */ |
| 504 | private static String getNai(Context context, Bundle mmsConfig, int subId) { |
| 505 | final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService( |
| 506 | Context.TELEPHONY_SERVICE); |
Sanket Padawe | 5ce5c2e | 2017-03-20 15:07:06 -0700 | [diff] [blame] | 507 | String nai = telephonyManager.getNai(SubscriptionManager.getSlotIndex(subId)); |
Ye Wen | 8153aed | 2015-06-16 13:36:31 -0700 | [diff] [blame] | 508 | if (LogUtil.isLoggable(Log.VERBOSE)) { |
| 509 | LogUtil.v("getNai: nai=" + nai); |
Jonathan Basseri | 4467007 | 2015-06-09 11:46:26 -0700 | [diff] [blame] | 510 | } |
| 511 | |
| 512 | if (!TextUtils.isEmpty(nai)) { |
| 513 | String naiSuffix = mmsConfig.getString(SmsManager.MMS_CONFIG_NAI_SUFFIX); |
| 514 | if (!TextUtils.isEmpty(naiSuffix)) { |
| 515 | nai = nai + naiSuffix; |
| 516 | } |
| 517 | byte[] encoded = null; |
| 518 | try { |
| 519 | encoded = Base64.encode(nai.getBytes("UTF-8"), Base64.NO_WRAP); |
| 520 | } catch (UnsupportedEncodingException e) { |
| 521 | encoded = Base64.encode(nai.getBytes(), Base64.NO_WRAP); |
| 522 | } |
| 523 | try { |
| 524 | nai = new String(encoded, "UTF-8"); |
| 525 | } catch (UnsupportedEncodingException e) { |
| 526 | nai = new String(encoded); |
| 527 | } |
| 528 | } |
| 529 | return nai; |
| 530 | } |
Ye Wen | bdb6fe1 | 2014-10-30 10:49:05 -0700 | [diff] [blame] | 531 | } |