package net.assemble.emailnotify.core; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.Locale; import java.util.Map.Entry; import android.os.Parcel; import android.os.Parcelable; import com.android.internal.telephony.WspTypeDecoder; import net.assemble.emailnotify.core.preferences.EmailNotifyPreferences; import net.orleaf.android.HexUtils; /** * WAP PDU 解析 */ public class WapPdu implements Parcelable { private byte[] wapData; private int dataIndex = -1; private String contentType; private int binaryContentType; private String applicationId; private int binaryApplicationId; private String mailBox = "unknown"; private byte[] timestamp = null; private String serviceName = null; private String errorMessage = null; // Content-Type private static final HashMap<Integer, String> CONTENTTYPES; static { CONTENTTYPES = new HashMap<Integer, String>(); CONTENTTYPES.put(WspTypeDecoder.CONTENT_TYPE_B_DRM_RIGHTS_XML, WspTypeDecoder.CONTENT_MIME_TYPE_B_DRM_RIGHTS_XML); CONTENTTYPES.put(WspTypeDecoder.CONTENT_TYPE_B_DRM_RIGHTS_WBXML, WspTypeDecoder.CONTENT_MIME_TYPE_B_DRM_RIGHTS_WBXML); CONTENTTYPES.put(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SI, WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_SI); CONTENTTYPES.put(WspTypeDecoder.CONTENT_TYPE_B_PUSH_SL, WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_SL); CONTENTTYPES.put(WspTypeDecoder.CONTENT_TYPE_B_PUSH_CO, WspTypeDecoder.CONTENT_MIME_TYPE_B_PUSH_CO); CONTENTTYPES.put(WspTypeDecoder.CONTENT_TYPE_B_MMS, WspTypeDecoder.CONTENT_MIME_TYPE_B_MMS); CONTENTTYPES.put(0x030a, "application/vnd.wap.emn+wbxml"); CONTENTTYPES.put(0x0310, "application/vnd.docomo.pf"); CONTENTTYPES.put(0x0311, "application/vnd.docomo.ub"); } // X-Wap-Application-Id private static final HashMap<Integer, String> APPIDS; static { APPIDS = new HashMap<Integer, String>(); APPIDS.put(0x09, "x-wap-application:emn.ua"); APPIDS.put(0x8002, "x-wap-docomo:imode.mail.ua"); APPIDS.put(0x8003, "x-wap-docomo:imode.mr.ua"); APPIDS.put(0x8004, "x-wap-docomo:imode.mf.ua"); APPIDS.put(0x9000, "x-wap-docomo:imode.mail2.ua"); APPIDS.put(0x9056, "x-oma-docomo:sp.mail.ua"); APPIDS.put(0x905c, "x-oma-docomo:xmd.mail.ua"); APPIDS.put(0x905e, "x-oma-docomo:imode.relation.ua"); APPIDS.put(0x905f, "x-oma-docomo:xmd.storage.ua"); APPIDS.put(0x9060, "x-oma-docomo:xmd.lcsapp.ua"); APPIDS.put(0x9061, "x-oma-docomo:xmd.info.ua"); APPIDS.put(0x9062, "x-oma-docomo:xmd.agent.ua"); APPIDS.put(0x9063, "x-oma-docomo:xmd.sab.ua"); APPIDS.put(0x9064, "x-oma-docomo:xmd.am.ua"); APPIDS.put(0x906b, "x-oma-docomo:xmd.emdm.ua"); } public WapPdu(Parcel in) { wapData = in.createByteArray(); dataIndex = in.readInt(); contentType = in.readString(); binaryContentType = in.readInt(); applicationId = in.readString(); binaryApplicationId = in.readInt(); mailBox = in.readString(); timestamp = in.createByteArray(); serviceName = in.readString(); } @Override public void writeToParcel(Parcel dest, int flags) { dest.writeByteArray(wapData); dest.writeInt(dataIndex); dest.writeString(contentType); dest.writeInt(binaryContentType); dest.writeString(applicationId); dest.writeInt(binaryApplicationId); dest.writeString(mailBox); dest.writeByteArray(timestamp); dest.writeString(serviceName); } @Override public int describeContents() { return 0; } public static final Parcelable.Creator<WapPdu> CREATOR = new Parcelable.Creator<WapPdu>() { public WapPdu createFromParcel(Parcel in) { return new WapPdu(in); } public WapPdu[] newArray(int size) { return new WapPdu[size]; } }; /** * Constructor * * WAP ボディのみ * * @param ctype Content-Type (string) * @param appid X-Wap-Application-Id (binary) * @param body WAP body */ public WapPdu(String ctype, int appid, byte[] body) { contentType = ctype; binaryContentType = convertMap(CONTENTTYPES, contentType); binaryApplicationId = appid; applicationId = convertMap(APPIDS, binaryApplicationId); // wapDataはボディ以降を示すため、dataIndexには0を設定 wapData = body; dataIndex = 0; } /** * Constructor * * @param pdu WAP PDU */ public WapPdu(byte[] pdu) { wapData = pdu; } /** * Constructor * * @param header WAP header * @param body WAP body */ public WapPdu(byte[] header, byte[] body) { wapData = new byte[3 + header.length + body.length]; wapData[0] = 0x00; wapData[1] = 0x06; wapData[2] = (byte) header.length; System.arraycopy(header, 0, wapData, 3, header.length); for (int i = 0; i < body.length; i++) { wapData[3 + header.length + i] = body[i]; } } /** * Constructor * * WAPの生データがないがサービスだけ特定できた場合 */ public WapPdu(String service, String mailbox) { wapData = null; serviceName = service; mailBox = mailbox; } /** * WAP PDU解析 (基本ぱくり) * * frameworks/base/telephony/java/com/android/internal/telephony/WapPushOverSms.java * WapPushOverSms#dispatchWapPdu() * * @return true:解析成功 */ public boolean decode() { if (dataIndex < 0) { int index = 0; @SuppressWarnings("unused") int transactionId = wapData[index++] & 0xFF; int pduType = wapData[index++] & 0xFF; int headerLength; try { if ((pduType != WspTypeDecoder.PDU_TYPE_PUSH) && (pduType != WspTypeDecoder.PDU_TYPE_CONFIRMED_PUSH)) { errorMessage = "WapPdu: non-PUSH WAP PDU. Type = " + pduType; return false; } WspTypeDecoder pduDecoder = new WspTypeDecoder(wapData); /** * Parse HeaderLen(unsigned integer). * From wap-230-wsp-20010705-a section 8.1.2 * The maximum size of a uintvar is 32 bits. * So it will be encoded in no more than 5 octets. */ if (!pduDecoder.decodeUintvarInteger(index)) { errorMessage = "WapPdu: Header Length error."; return false; } headerLength = (int)pduDecoder.getValue32(); index += pduDecoder.getDecodedDataLength(); int headerStartIndex = index; /** * Parse Content-Type. * From wap-230-wsp-20010705-a section 8.4.2.24 * * Content-type-value = Constrained-media | Content-general-form * Content-general-form = Value-length Media-type * Media-type = (Well-known-media | Extension-Media) *(Parameter) * Value-length = Short-length | (Length-quote Length) * Short-length = <Any octet 0-30> (octet <= WAP_PDU_SHORT_LENGTH_MAX) * Length-quote = <Octet 31> (WAP_PDU_LENGTH_QUOTE) * Length = Uintvar-integer */ if (!pduDecoder.decodeContentType(index)) { errorMessage = "WapPdu: Header Content-Type error."; return false; } contentType = pduDecoder.getValueString(); if (contentType == null) { binaryContentType = (int)pduDecoder.getValue32(); contentType = convertMap(CONTENTTYPES, binaryContentType); } else { binaryContentType = convertMap(CONTENTTYPES, contentType); } index += pduDecoder.getDecodedDataLength(); dataIndex = headerStartIndex + headerLength; /** * Parse X-Wap-Application-Id. * From wap-230-wsp-20010705-a section 8.4.2.54 * * Application-id-value = Uri-value | App-assigned-code * App-assigned-code = Integer-value */ if (wapData[index] == 0xaf - 0x100) { if (!pduDecoder.decodeXWapApplicationId(index + 1)) { errorMessage = "WapPdu: Header X-Wap-Application-Id error."; return false; } applicationId = pduDecoder.getValueString(); if (applicationId == null) { binaryApplicationId = (int)pduDecoder.getValue32(); applicationId = convertMap(APPIDS, binaryApplicationId); } else { binaryApplicationId = convertMap(APPIDS, applicationId); } index += pduDecoder.getDecodedDataLength() + 1; } else { errorMessage = "WapPdu: Header X-Wap-Application-Id not present." + wapData[index]; return false; } } catch (IndexOutOfBoundsException e) { errorMessage = "WapPdu: PDU decode error."; return false; } } decodeBody(); return true; } /** * ボディ部のデコード処理 * * mailbox/timestamp属性を取得する。 */ public void decodeBody() { int index = dataIndex; try { if (binaryContentType == 0x030a || // application/vnd.wap.emn+wbxml binaryContentType == WspTypeDecoder.CONTENT_TYPE_B_PUSH_SL) { index += 5; while (true) { if (wapData[index] == 0x05) { // timestamp attribute if (wapData[index + 1] + 0x100 == 0xc3) { index += 2; int tsLen = wapData[index]; timestamp = new byte[tsLen]; index++; System.arraycopy(wapData, index, timestamp, 0, tsLen); index += tsLen; } } else if (0x06 <= wapData[index] && wapData[index] <= 0x0d) { // mailbox attribute String prefix = ""; switch (wapData[index]) { case 0x07: prefix = "mailat:"; break; case 0x08: prefix = "pop://"; break; case 0x09: prefix = "imap://"; break; case 0x0a: prefix = "http://"; break; case 0x0b: prefix = "http://www."; break; case 0x0c: prefix = "https://"; break; case 0x0d: prefix = "https://www."; break; } index += 2; // mailat: int strLen = 0; for (int i = index; wapData[i] != 0; i++) { strLen++; } byte[] m = new byte[strLen]; System.arraycopy(wapData, index, m, 0, strLen); mailBox = prefix + new String(m, 0); index += strLen + 1; int tld = wapData[index]; if (tld < 0) { tld += 0x100; switch (tld) { case 0x85: mailBox += ".com"; break; case 0x86: mailBox += ".edu"; break; case 0x87: mailBox += ".net"; break; case 0x88: mailBox += ".org"; break; } index++; } } else { break; } } } } catch (IndexOutOfBoundsException e) { errorMessage = "WapPdu: PDU analyze error."; } } /** * key -> val (バイナリ値→文字列) */ private String convertMap(HashMap<Integer, String> map, int key) { if (map.containsKey(key)) { return map.get(key); } // unknown return "unknown(" + String.format("0x%x", key) + ")"; } /** * val -> key (文字列→バイナリ値) */ private int convertMap(HashMap<Integer, String> map, String val) { if (map.containsValue(val)) { for (Entry<Integer, String> entry : map.entrySet()) { Integer key = entry.getKey(); String value = entry.getValue(); if (value.equals(val)) { return key; } } } // unknown return 0; } /** * デコードされたContent-Type(文字列)を取得 * * @return Content-Type文字列 */ public String getContentType() { return contentType; } /** * デコードされたContent-Type(バイナリ値)を取得 * * @return Content-Typeバイナリ値 */ public int getBinaryContentType() { return binaryContentType; } /** * デコードされたX-Wap-Application-Id(文字列)を取得 * * @return X-Wap-Application-Id文字列 */ public String getApplicationId() { return applicationId; } /** * デコードされたX-Wap-Application-Id(バイナリ値)を取得 * * @return X-Wap-Application-Idバイナリ値 */ public int getBinaryApplicationId() { return binaryApplicationId; } /** * デコードされたボディのmailbox属性を取得 * * @return mailbox属性 */ public String getMailbox() { return mailBox; } /** * デコードされたボディのtimestamp属性を取得 * * @return timestamp属性 */ public String getTimestampString() { if (timestamp != null) { return HexUtils.bytes2hex(timestamp); } else { return null; } } /** * デコードされたボディのtimestamp属性をDate型で取得 * * @return timestamp属性 */ public Date getTimestampDate() { Date date = null; if (timestamp != null) { try { SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss z", Locale.US); date = sdf.parse(HexUtils.bytes2hex(timestamp) + " GMT"); } catch (ParseException e) { //Log.w(EmailNotify.TAG, "WapPdu: Unexpected timestamp: " + HexUtils.bytes2hex(timestamp)); } } return date; } /** * データの16進数文字列を取得 * * @return 16進数文字列 */ public String getHexString() { if (wapData != null) { return HexUtils.bytes2hex(wapData); } return null; } /** * サービス名を取得 * * @return サービス名 */ public String getServiceName() { if (serviceName == null) { serviceName = getServiceName(contentType, mailBox); } return serviceName; } /** * サービス名を取得 * * @return サービス名 */ private static String getServiceName(String cype, String mailbox) { String service; // メールサービス別通知 if (cype != null && cype.equals("application/vnd.wap.emn+wbxml") && mailbox != null && mailbox.endsWith("docomo.ne.jp")) { // spモードメール service = EmailNotifyPreferences.SERVICE_SPMODE; } else if (cype != null && cype.equals("application/vnd.wap.emn+wbxml") && mailbox != null && mailbox.endsWith("mopera.net")) { // mopera Uメール service = EmailNotifyPreferences.SERVICE_MOPERA; } else if (cype != null && cype.equals("application/vnd.wap.slc") && mailbox != null && mailbox.contains("docomo.ne.jp")) { // iモードメール service = EmailNotifyPreferences.SERVICE_IMODE; } else { // その他 service = EmailNotifyPreferences.SERVICE_UNKNOWN; } return service; } /** * エラーメッセージを取得 */ @SuppressWarnings("unused") public String getErrorMessage() { return errorMessage; } }