summaryrefslogtreecommitdiffstats
path: root/src/android/bluetooth/client/map/BluetoothMapBmessageParser.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/android/bluetooth/client/map/BluetoothMapBmessageParser.java')
-rw-r--r--src/android/bluetooth/client/map/BluetoothMapBmessageParser.java421
1 files changed, 421 insertions, 0 deletions
diff --git a/src/android/bluetooth/client/map/BluetoothMapBmessageParser.java b/src/android/bluetooth/client/map/BluetoothMapBmessageParser.java
new file mode 100644
index 0000000..fa3d817
--- /dev/null
+++ b/src/android/bluetooth/client/map/BluetoothMapBmessageParser.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.bluetooth.client.map;
+
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardEntryHandler;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+import com.android.vcard.exception.VCardVersionException;
+import android.bluetooth.client.map.BluetoothMapBmessage.Status;
+import android.bluetooth.client.map.BluetoothMapBmessage.Type;
+import android.bluetooth.client.map.utils.BmsgTokenizer;
+import android.bluetooth.client.map.utils.BmsgTokenizer.Property;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.text.ParseException;
+
+class BluetoothMapBmessageParser {
+
+ private final static String TAG = "BluetoothMapBmessageParser";
+
+ private final static String CRLF = "\r\n";
+
+ private final static Property BEGIN_BMSG = new Property("BEGIN", "BMSG");
+ private final static Property END_BMSG = new Property("END", "BMSG");
+
+ private final static Property BEGIN_VCARD = new Property("BEGIN", "VCARD");
+ private final static Property END_VCARD = new Property("END", "VCARD");
+
+ private final static Property BEGIN_BENV = new Property("BEGIN", "BENV");
+ private final static Property END_BENV = new Property("END", "BENV");
+
+ private final static Property BEGIN_BBODY = new Property("BEGIN", "BBODY");
+ private final static Property END_BBODY = new Property("END", "BBODY");
+
+ private final static Property BEGIN_MSG = new Property("BEGIN", "MSG");
+ private final static Property END_MSG = new Property("END", "MSG");
+
+ private final static int CRLF_LEN = 2;
+
+ /*
+ * length of "container" for 'message' in bmessage-body-content:
+ * BEGIN:MSG<CRLF> + <CRLF> + END:MSG<CRFL>
+ */
+ private final static int MSG_CONTAINER_LEN = 22;
+
+ private BmsgTokenizer mParser;
+
+ private final BluetoothMapBmessage mBmsg;
+
+ private BluetoothMapBmessageParser() {
+ mBmsg = new BluetoothMapBmessage();
+ }
+
+ static public BluetoothMapBmessage createBmessage(String str) {
+ BluetoothMapBmessageParser p = new BluetoothMapBmessageParser();
+
+ try {
+ p.parse(str);
+ } catch (IOException e) {
+ Log.e(TAG, "I/O exception when parsing bMessage", e);
+ return null;
+ } catch (ParseException e) {
+ Log.e(TAG, "Cannot parse bMessage", e);
+ return null;
+ }
+
+ return p.mBmsg;
+ }
+
+ private ParseException expected(Property... props) {
+ boolean first = true;
+ StringBuilder sb = new StringBuilder();
+
+ for (Property prop : props) {
+ if (!first) {
+ sb.append(" or ");
+ }
+ sb.append(prop);
+ first = false;
+ }
+
+ return new ParseException("Expected: " + sb.toString(), mParser.pos());
+ }
+
+ private void parse(String str) throws IOException, ParseException {
+
+ Property prop;
+
+ /*
+ * <bmessage-object>::= { "BEGIN:BMSG" <CRLF> <bmessage-property>
+ * [<bmessage-originator>]* <bmessage-envelope> "END:BMSG" <CRLF> }
+ */
+
+ mParser = new BmsgTokenizer(str + CRLF);
+
+ prop = mParser.next();
+ if (!prop.equals(BEGIN_BMSG)) {
+ throw expected(BEGIN_BMSG);
+ }
+
+ prop = parseProperties();
+
+ while (prop.equals(BEGIN_VCARD)) {
+
+ /* <bmessage-originator>::= <vcard> <CRLF> */
+
+ StringBuilder vcard = new StringBuilder();
+ prop = extractVcard(vcard);
+
+ VCardEntry entry = parseVcard(vcard.toString());
+ mBmsg.mOriginators.add(entry);
+ }
+
+ if (!prop.equals(BEGIN_BENV)) {
+ throw expected(BEGIN_BENV);
+ }
+
+ prop = parseEnvelope(1);
+
+ if (!prop.equals(END_BMSG)) {
+ throw expected(END_BENV);
+ }
+
+ /*
+ * there should be no meaningful data left in stream here so we just
+ * ignore whatever is left
+ */
+
+ mParser = null;
+ }
+
+ private Property parseProperties() throws ParseException {
+
+ Property prop;
+
+ /*
+ * <bmessage-property>::=<bmessage-version-property>
+ * <bmessage-readstatus-property> <bmessage-type-property>
+ * <bmessage-folder-property> <bmessage-version-property>::="VERSION:"
+ * <common-digit>*"."<common-digit>* <CRLF>
+ * <bmessage-readstatus-property>::="STATUS:" 'readstatus' <CRLF>
+ * <bmessage-type-property>::="TYPE:" 'type' <CRLF>
+ * <bmessage-folder-property>::="FOLDER:" 'foldername' <CRLF>
+ */
+
+ do {
+ prop = mParser.next();
+
+ if (prop.name.equals("VERSION")) {
+ mBmsg.mBmsgVersion = prop.value;
+
+ } else if (prop.name.equals("STATUS")) {
+ for (Status s : Status.values()) {
+ if (prop.value.equals(s.toString())) {
+ mBmsg.mBmsgStatus = s;
+ break;
+ }
+ }
+
+ } else if (prop.name.equals("TYPE")) {
+ for (Type t : Type.values()) {
+ if (prop.value.equals(t.toString())) {
+ mBmsg.mBmsgType = t;
+ break;
+ }
+ }
+
+ } else if (prop.name.equals("FOLDER")) {
+ mBmsg.mBmsgFolder = prop.value;
+
+ }
+
+ } while (!prop.equals(BEGIN_VCARD) && !prop.equals(BEGIN_BENV));
+
+ return prop;
+ }
+
+ private Property parseEnvelope(int level) throws IOException, ParseException {
+
+ Property prop;
+
+ /*
+ * we can support as many nesting level as we want, but MAP spec clearly
+ * defines that there should be no more than 3 levels. so we verify it
+ * here.
+ */
+
+ if (level > 3) {
+ throw new ParseException("bEnvelope is nested more than 3 times", mParser.pos());
+ }
+
+ /*
+ * <bmessage-envelope> ::= { "BEGIN:BENV" <CRLF> [<bmessage-recipient>]*
+ * <bmessage-envelope> | <bmessage-content> "END:BENV" <CRLF> }
+ */
+
+ prop = mParser.next();
+
+ while (prop.equals(BEGIN_VCARD)) {
+
+ /* <bmessage-originator>::= <vcard> <CRLF> */
+
+ StringBuilder vcard = new StringBuilder();
+ prop = extractVcard(vcard);
+
+ if (level == 1) {
+ VCardEntry entry = parseVcard(vcard.toString());
+ mBmsg.mRecipients.add(entry);
+ }
+ }
+
+ if (prop.equals(BEGIN_BENV)) {
+ prop = parseEnvelope(level + 1);
+
+ } else if (prop.equals(BEGIN_BBODY)) {
+ prop = parseBody();
+
+ } else {
+ throw expected(BEGIN_BENV, BEGIN_BBODY);
+ }
+
+ if (!prop.equals(END_BENV)) {
+ throw expected(END_BENV);
+ }
+
+ return mParser.next();
+ }
+
+ private Property parseBody() throws IOException, ParseException {
+
+ Property prop;
+
+ /*
+ * <bmessage-content>::= { "BEGIN:BBODY"<CRLF> [<bmessage-body-part-ID>
+ * <CRLF>] <bmessage-body-property> <bmessage-body-content>* <CRLF>
+ * "END:BBODY"<CRLF> } <bmessage-body-part-ID>::="PARTID:" 'Part-ID'
+ * <bmessage-body-property>::=[<bmessage-body-encoding-property>]
+ * [<bmessage-body-charset-property>]
+ * [<bmessage-body-language-property>]
+ * <bmessage-body-content-length-property>
+ * <bmessage-body-encoding-property>::="ENCODING:"'encoding' <CRLF>
+ * <bmessage-body-charset-property>::="CHARSET:"'charset' <CRLF>
+ * <bmessage-body-language-property>::="LANGUAGE:"'language' <CRLF>
+ * <bmessage-body-content-length-property>::= "LENGTH:" <common-digit>*
+ * <CRLF>
+ */
+
+ do {
+ prop = mParser.next();
+
+ if (prop.name.equals("PARTID")) {
+ } else if (prop.name.equals("ENCODING")) {
+ mBmsg.mBbodyEncoding = prop.value;
+
+ } else if (prop.name.equals("CHARSET")) {
+ mBmsg.mBbodyCharset = prop.value;
+
+ } else if (prop.name.equals("LANGUAGE")) {
+ mBmsg.mBbodyLanguage = prop.value;
+
+ } else if (prop.name.equals("LENGTH")) {
+ try {
+ mBmsg.mBbodyLength = Integer.valueOf(prop.value);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Invalid LENGTH value", mParser.pos());
+ }
+
+ }
+
+ } while (!prop.equals(BEGIN_MSG));
+
+ /*
+ * <bmessage-body-content>::={ "BEGIN:MSG"<CRLF> 'message'<CRLF>
+ * "END:MSG"<CRLF> }
+ */
+
+ int messageLen = mBmsg.mBbodyLength - MSG_CONTAINER_LEN;
+ int offset = messageLen + CRLF_LEN;
+ int restartPos = mParser.pos() + offset;
+
+ /*
+ * length is specified in bytes so we need to convert from unicode
+ * string back to bytes array
+ */
+
+ String remng = mParser.remaining();
+ byte[] data = remng.getBytes();
+
+ /* restart parsing from after 'message'<CRLF> */
+ mParser = new BmsgTokenizer(new String(data, offset, data.length - offset), restartPos);
+
+ prop = mParser.next(true);
+
+ if (prop != null && prop.equals(END_MSG)) {
+ mBmsg.mMessage = new String(data, 0, messageLen);
+ } else {
+
+ data = null;
+
+ /*
+ * now we check if bMessage can be parsed if LENGTH is handled as
+ * number of characters instead of number of bytes
+ */
+
+ Log.w(TAG, "byte LENGTH seems to be invalid, trying with char length");
+
+ mParser = new BmsgTokenizer(remng.substring(offset));
+
+ prop = mParser.next();
+
+ if (!prop.equals(END_MSG)) {
+ throw expected(END_MSG);
+ }
+
+ mBmsg.mMessage = remng.substring(0, messageLen);
+ }
+
+ prop = mParser.next();
+
+ if (!prop.equals(END_BBODY)) {
+ throw expected(END_BBODY);
+ }
+
+ return mParser.next();
+ }
+
+ private Property extractVcard(StringBuilder out) throws IOException, ParseException {
+ Property prop;
+
+ out.append(BEGIN_VCARD).append(CRLF);
+
+ do {
+ prop = mParser.next();
+ out.append(prop).append(CRLF);
+ } while (!prop.equals(END_VCARD));
+
+ return mParser.next();
+ }
+
+ private class VcardHandler implements VCardEntryHandler {
+
+ VCardEntry vcard;
+
+ @Override
+ public void onStart() {
+ }
+
+ @Override
+ public void onEntryCreated(VCardEntry entry) {
+ vcard = entry;
+ }
+
+ @Override
+ public void onEnd() {
+ }
+ };
+
+ private VCardEntry parseVcard(String str) throws IOException, ParseException {
+ VCardEntry vcard = null;
+
+ try {
+ VCardParser p = new VCardParser_V21();
+ VCardEntryConstructor c = new VCardEntryConstructor();
+ VcardHandler handler = new VcardHandler();
+ c.addEntryHandler(handler);
+ p.addInterpreter(c);
+ p.parse(new ByteArrayInputStream(str.getBytes()));
+
+ vcard = handler.vcard;
+
+ } catch (VCardVersionException e1) {
+
+ try {
+ VCardParser p = new VCardParser_V30();
+ VCardEntryConstructor c = new VCardEntryConstructor();
+ VcardHandler handler = new VcardHandler();
+ c.addEntryHandler(handler);
+ p.addInterpreter(c);
+ p.parse(new ByteArrayInputStream(str.getBytes()));
+
+ vcard = handler.vcard;
+
+ } catch (VCardVersionException e2) {
+ // will throw below
+ } catch (VCardException e2) {
+ // will throw below
+ }
+
+ } catch (VCardException e1) {
+ // will throw below
+ }
+
+ if (vcard == null) {
+ throw new ParseException("Cannot parse vCard object (neither 2.1 nor 3.0?)",
+ mParser.pos());
+ }
+
+ return vcard;
+ }
+}